From 211ed2d9a5681f11242233adfd5a1cdee2ebf3ac Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Wed, 17 Dec 2025 16:30:45 -0600 Subject: [PATCH 01/53] Add CDK resources for military audit workflow --- .../api_lambda_stack/provider_management.py | 35 +++++++++++++++++++ .../stacks/api_stack/v1_api/api_model.py | 27 ++++++++++++++ .../api_stack/v1_api/provider_management.py | 30 ++++++++++++++++ .../stacks/notification_stack.py | 17 +++++++++ 4 files changed, 109 insertions(+) diff --git a/backend/compact-connect/stacks/api_lambda_stack/provider_management.py b/backend/compact-connect/stacks/api_lambda_stack/provider_management.py index 8253981ed..c83475f26 100644 --- a/backend/compact-connect/stacks/api_lambda_stack/provider_management.py +++ b/backend/compact-connect/stacks/api_lambda_stack/provider_management.py @@ -57,6 +57,8 @@ def __init__( 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) + self.military_audit_handler = self._add_military_audit_handler(lambda_environment) + api_lambda_stack.log_groups.append(self.military_audit_handler.log_group) def _create_provider_investigation_handler(self, lambda_environment: dict) -> PythonFunction: """Create and configure the Lambda handler for investigating a provider's privilege or license.""" @@ -369,3 +371,36 @@ def _add_provider_encumbrance_handler( ) return handler + + def _add_military_audit_handler( + self, + lambda_environment: dict, + ) -> PythonFunction: + """Create and configure the Lambda handler for auditing military affiliation records.""" + handler = PythonFunction( + self.scope, + 'MilitaryAuditHandler', + description='Military audit handler', + lambda_dir='provider-data-v1', + index=os.path.join('handlers', 'military_audit.py'), + handler='military_audit_handler', + 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) + + 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 the needed tables and event bus.', + }, + ], + ) + + return handler diff --git a/backend/compact-connect/stacks/api_stack/v1_api/api_model.py b/backend/compact-connect/stacks/api_stack/v1_api/api_model.py index 531158bcc..e06d5d341 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/api_model.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/api_model.py @@ -2876,3 +2876,30 @@ def patch_license_investigation_request_model(self) -> Model: ), ) return self.api._v1_patch_license_investigation_request_model + + @property + def patch_military_audit_request_model(self) -> Model: + """PATCH military audit request model""" + if not hasattr(self.api, '_v1_patch_military_audit_request_model'): + self.api._v1_patch_military_audit_request_model = self.api.add_model( + 'V1PatchMilitaryAuditRequestModel', + description='Patch military audit request model', + schema=JsonSchema( + type=JsonSchemaType.OBJECT, + additional_properties=False, + required=['militaryStatus'], + properties={ + 'militaryStatus': JsonSchema( + type=JsonSchemaType.STRING, + enum=['approved', 'declined'], + description='The audit result for the military documentation', + ), + 'militaryStatusNote': JsonSchema( + type=JsonSchemaType.STRING, + description='Optional note from the admin (typically for declines)', + max_length=5000, + ), + }, + ), + ) + return self.api._v1_patch_military_audit_request_model diff --git a/backend/compact-connect/stacks/api_stack/v1_api/provider_management.py b/backend/compact-connect/stacks/api_stack/v1_api/provider_management.py index df50ec15e..bd01159bc 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/provider_management.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/provider_management.py @@ -99,6 +99,11 @@ def __init__( privilege_history_function=privilege_history_function, ) + self._add_military_audit( + method_options=admin_method_options, + military_audit_handler=api_lambda_stack.provider_management_lambdas.military_audit_handler, + ) + def _add_get_provider( self, method_options: MethodOptions, @@ -449,3 +454,28 @@ def _add_get_privilege_history( authorizer=method_options.authorizer, authorization_scopes=method_options.authorization_scopes, ) + + def _add_military_audit( + self, + method_options: MethodOptions, + military_audit_handler: PythonFunction, + ): + """Add PATCH /providers/{providerId}/militaryAudit endpoint for compact admins to audit + military affiliation records.""" + self.military_audit_resource = self.provider_resource.add_resource('militaryAudit') + self.military_audit_resource.add_method( + 'PATCH', + request_validator=self.api.parameter_body_validator, + request_models={'application/json': self.api_model.patch_military_audit_request_model}, + method_responses=[ + MethodResponse( + status_code='200', + response_models={'application/json': self.api_model.message_response_model}, + ), + ], + integration=LambdaIntegration(military_audit_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/compact-connect/stacks/notification_stack.py b/backend/compact-connect/stacks/notification_stack.py index fdd9cac90..f5266a9d0 100644 --- a/backend/compact-connect/stacks/notification_stack.py +++ b/backend/compact-connect/stacks/notification_stack.py @@ -67,6 +67,9 @@ def __init__( self._add_privilege_investigation_closed_notification_listener( persistent_stack=persistent_stack, data_event_bus=data_event_bus, event_state_stack=event_state_stack ) + self._add_military_audit_notification_listener( + 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 @@ -343,3 +346,17 @@ def _add_privilege_investigation_closed_notification_listener( data_event_bus=data_event_bus, event_state_stack=event_state_stack, ) + + def _add_military_audit_notification_listener( + self, persistent_stack: ps.PersistentStack, data_event_bus: EventBus, event_state_stack: ess.EventStateStack + ): + """Add the military audit notification listener lambda, queues, and event rules.""" + self._add_emailer_event_listener( + construct_id_prefix='MilitaryAuditNotificationListener', + index='military_audit_events.py', + handler='military_audit_notification_listener', + listener_detail_type='militaryAffiliation.audit', + persistent_stack=persistent_stack, + data_event_bus=data_event_bus, + event_state_stack=event_state_stack, + ) From 8a7d8485b1c915bab08b840a9116116f0be23020 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Wed, 17 Dec 2025 16:47:46 -0600 Subject: [PATCH 02/53] Initial implementation of military audit endpoint with schema updates --- .../cc_common/data_model/data_client.py | 67 ++++++ .../data_model/provider_record_util.py | 10 + .../cc_common/data_model/schema/common.py | 8 + .../cc_common/data_model/schema/fields.py | 6 + .../data_model/schema/provider/__init__.py | 19 ++ .../data_model/schema/provider/api.py | 9 + .../data_model/schema/provider/record.py | 19 +- .../common/cc_common/event_bus_client.py | 40 ++++ .../handlers/military_audit.py | 94 ++++++++ .../test_handlers/test_military_audit.py | 212 ++++++++++++++++++ 10 files changed, 483 insertions(+), 1 deletion(-) create mode 100644 backend/compact-connect/lambdas/python/provider-data-v1/handlers/military_audit.py create mode 100644 backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_military_audit.py diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py index 57204345e..619258bf1 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py @@ -27,6 +27,7 @@ InvestigationStatusEnum, LicenseDeactivatedStatusEnum, LicenseEncumberedStatusEnum, + MilitaryAuditStatus, PrivilegeEncumberedStatusEnum, UpdateCategory, ) @@ -787,6 +788,72 @@ def inactivate_military_affiliation_status(self, compact: str, provider_id: str) serialized_record = record.serialize_to_database_record() batch.put_item(Item=serialized_record) + @logger_inject_kwargs(logger, 'compact', 'provider_id', 'military_status') + def process_military_audit( + self, + *, + compact: str, + provider_id: UUID, + military_status: MilitaryAuditStatus, + military_status_note: str | None = None, + ) -> None: + """ + Update provider and latest military affiliation with audit result in a transaction. + + This method: + 1. Gets the provider record + 2. Updates provider record with militaryStatus and militaryStatusNote + 3. Creates provider update record with updated values + 4. Executes both updates in a DynamoDB transaction + + :param compact: The compact name + :param provider_id: The provider id + :param military_status: The audit result status (approved or declined) + :param military_status_note: Optional note from the admin (typically for declines) + :raises CCNotFoundException: If provider or military affiliation not found + """ + logger.info('Processing military audit') + + # Get provider records + provider_user_records = self.get_provider_user_records(compact=compact, provider_id=provider_id) + provider_record = provider_user_records.get_provider_record() + latest_military_affiliation = provider_user_records.get_latest_military_affiliation() + + if not latest_military_affiliation: + logger.error('No military affiliation record found for provider') + raise CCNotFoundException('No military affiliation records found for this provider') + + # Prepare the note value (empty string if not provided) + note_value = military_status_note or '' + + # Update provider record with military status + provider_record.update( + { + 'militaryStatus': military_status.value, + 'militaryStatusNote': note_value, + } + ) + + #TODO - create provider update record to track any previous values + + # Execute both updates in a transaction + self.config.dynamodb_client.transact_write_items( + TransactItems=[ + # Update provider record + { + 'Put': { + 'TableName': self.config.provider_table_name, + 'Item': TypeSerializer().serialize(provider_record.serialize_to_database_record())['M'], + } + }, + ] + ) + + logger.info( + 'Military audit processed successfully', + military_status=military_status.value, + ) + @logger_inject_kwargs(logger, 'compact', 'provider_ids') def batch_get_providers_by_id(self, compact: str, provider_ids: list[str]) -> list[dict]: """ diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py index c8b1f74c0..cac637925 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py @@ -770,6 +770,16 @@ def get_latest_military_affiliation_status(self) -> str | None: return latest_military_affiliation.status + def get_latest_military_affiliation(self) -> MilitaryAffiliationData | None: + """ + Get the most recent military affiliation record for this provider. + :return: The most recent military affiliation record if present, else None + """ + if not self._military_affiliation_records: + return None + + return sorted(self._military_affiliation_records, key=lambda x: x.dateOfUpload, reverse=True)[0] + def get_all_license_update_records( self, filter_condition: Callable[[LicenseUpdateData], bool] | None = None, diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py index ae058c729..5039a0d6b 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py @@ -292,6 +292,14 @@ class InvestigationAgainstEnum(StrEnum): PRIVILEGE = 'privilege' LICENSE = 'license' +class MilitaryAuditStatus(CCEnum): + """Status of military documentation audit by compact admins.""" + + NOT_APPLICABLE = 'notApplicable' + TENTATIVE = 'tentative' + APPROVED = 'approved' + DECLINED = 'declined' + class UpdateCategory(CCEnum): DEACTIVATION = 'deactivation' diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py index eb4675bfa..a5d59503e 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py @@ -15,6 +15,7 @@ PrivilegeEncumberedStatusEnum, UpdateCategory, ) +from cc_common.data_model.schema.common import MilitaryAuditStatus # This is a special value that is used to indicate that the provider's home jurisdiction is not known. # This can happen if a provider moves to a jurisdiction that is not part of the compact. @@ -132,6 +133,11 @@ def __init__(self, *args, **kwargs): super().__init__(*args, validate=OneOf([entry.value for entry in InvestigationAgainstEnum]), **kwargs) +class MilitaryAuditStatusField(String): + def __init__(self, *args, **kwargs): + super().__init__(*args, validate=OneOf([entry.value for entry in MilitaryAuditStatus]), **kwargs) + + class PositiveDecimal(Decimal): """A Decimal field that validates the value is greater than or equal to 0.""" diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/__init__.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/__init__.py index ebbf23e4b..fc989c689 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/__init__.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/__init__.py @@ -147,6 +147,25 @@ def licenseStatus(self) -> str | None: def currentHomeJurisdiction(self) -> str | None: return self._data.get('currentHomeJurisdiction') + @property + def militaryStatus(self) -> str: + """ + The military audit status of the provider. + + Possible values: 'notApplicable', 'tentative', 'approved', 'declined' + Default is 'notApplicable' if no military documentation has been uploaded. + """ + return self._data.get('militaryStatus') + + @property + def militaryStatusNote(self) -> str: + """ + The note from the most recent military audit decision (if declined). + + Empty string if no note has been provided. + """ + return self._data.get('militaryStatusNote') + class ProviderUpdateData(CCDataClass): """ diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/api.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/api.py index 36d464caa..d8ebd84a2 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/api.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/api.py @@ -13,6 +13,7 @@ CompactEligibility, CurrentHomeJurisdictionField, Jurisdiction, + MilitaryAuditStatusField, NationalProviderIdentifier, Set, SocialSecurityNumber, @@ -127,6 +128,10 @@ class ProviderReadPrivateResponseSchema(ForgivingSchema): Nested(MilitaryAffiliationReadPrivateResponseSchema(), required=False, allow_none=False) ) + # Military audit status fields + militaryStatus = MilitaryAuditStatusField(required=False, allow_none=False) + militaryStatusNote = String(required=False, allow_none=False) + # these fields are specific to the read private role dateOfBirth = Raw(required=True, allow_none=False) ssnLastFour = String(required=False, allow_none=False, validate=Length(equal=4)) @@ -179,6 +184,10 @@ class ProviderGeneralResponseSchema(ForgivingSchema): privileges = List(Nested(PrivilegeGeneralResponseSchema(), required=False, allow_none=False)) militaryAffiliations = List(Nested(MilitaryAffiliationGeneralResponseSchema(), required=False, allow_none=False)) + # Military audit status fields + militaryStatus = MilitaryAuditStatusField(required=False, allow_none=False) + militaryStatusNote = String(required=False, allow_none=False) + class ProviderPublicResponseSchema(ForgivingSchema): """ diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py index 8835b2a88..18980408d 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py @@ -22,10 +22,12 @@ CurrentHomeJurisdictionField, Jurisdiction, LicenseEncumberedStatusField, + MilitaryAuditStatusField, NationalProviderIdentifier, Set, UpdateType, ) +from cc_common.data_model.schema.common import MilitaryAuditStatus from cc_common.data_model.update_tier_enum import UpdateTierEnum @@ -77,6 +79,10 @@ class ProviderRecordSchema(BaseRecordSchema): recoveryToken = String(required=False, allow_none=False) recoveryExpiry = DateTime(required=False, allow_none=False) + # Military audit status fields + militaryStatus = MilitaryAuditStatusField(required=False, allow_none=False) + militaryStatusNote = String(required=False, allow_none=False) + # 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()) @@ -93,7 +99,16 @@ class ProviderRecordSchema(BaseRecordSchema): def _calculate_statuses(self, in_data, **_kwargs): """Determine the statuses of the record based on the expiration date""" in_data = self._calculate_license_status(in_data) - return self._calculate_compact_eligibility(in_data) + in_data = self._calculate_compact_eligibility(in_data) + return self._set_military_status_defaults(in_data) + + def _set_military_status_defaults(self, in_data, **_kwargs): + """Set default values for military audit status fields if not present""" + if 'militaryStatus' not in in_data: + in_data['militaryStatus'] = MilitaryAuditStatus.NOT_APPLICABLE + if 'militaryStatusNote' not in in_data: + in_data['militaryStatusNote'] = '' + return in_data def _calculate_license_status(self, in_data, **_kwargs): """Determine the status of the license based on the expiration date""" @@ -206,6 +221,8 @@ class ProviderUpdatePreviousRecordSchema(ForgivingSchema): dateOfExpiration = Date(required=True, allow_none=False) dateOfBirth = Date(required=True, allow_none=False) compactConnectRegisteredEmailAddress = Email(required=False, allow_none=False) + militaryStatus = MilitaryAuditStatusField(required=False, allow_none=False) + militaryStatusNote = String(required=False, allow_none=False) currentHomeJurisdiction = CurrentHomeJurisdictionField(required=False, allow_none=False) dateOfUpdate = DateTime(required=True, allow_none=False) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/event_bus_client.py b/backend/compact-connect/lambdas/python/common/cc_common/event_bus_client.py index fe6af52b7..9a741bb02 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/event_bus_client.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/event_bus_client.py @@ -11,6 +11,7 @@ InvestigationEventDetailSchema, LicenseDeactivationDetailSchema, LicenseRevertDetailSchema, + MilitaryAuditEventDetailSchema, PrivilegeIssuanceDetailSchema, PrivilegePurchaseEventDetailSchema, PrivilegeRenewalDetailSchema, @@ -545,3 +546,42 @@ def publish_privilege_revert_event( detail=deserialized_detail, event_batch_writer=event_batch_writer, ) + + def publish_military_audit_event( + self, + source: str, + compact: str, + provider_id: UUID, + audit_result: str, + audit_note: str | None = None, + event_batch_writer: EventBatchWriter | None = None, + ): + """ + Publish a military audit event to the event bus. + + :param source: The source of the event + :param compact: The compact name + :param provider_id: The provider ID + :param audit_result: The audit result (approved or declined) + :param audit_note: Optional note from the admin + :param event_batch_writer: Optional EventBatchWriter for efficient batch publishing + """ + event_detail = { + 'compact': compact, + 'providerId': provider_id, + 'auditResult': audit_result, + 'eventTime': config.current_standard_datetime, + } + + if audit_note: + event_detail['auditNote'] = audit_note + + military_audit_detail_schema = MilitaryAuditEventDetailSchema() + deserialized_detail = military_audit_detail_schema.dump(event_detail) + + self._publish_event( + source=source, + detail_type='militaryAffiliation.audit', + detail=deserialized_detail, + event_batch_writer=event_batch_writer, + ) diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/military_audit.py b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/military_audit.py new file mode 100644 index 000000000..b59ba65b8 --- /dev/null +++ b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/military_audit.py @@ -0,0 +1,94 @@ +import json + +from aws_lambda_powertools.utilities.typing import LambdaContext +from marshmallow import Schema, ValidationError +from marshmallow.fields import String +from marshmallow.validate import OneOf + +from cc_common.config import config, logger +from cc_common.data_model.schema.common import MilitaryAuditStatus +from cc_common.exceptions import CCInvalidRequestException +from cc_common.utils import api_handler, authorize_compact_level_only_action, to_uuid +from cc_common.data_model.schema.common import CCPermissionsAction + + +class MilitaryAuditRequestSchema(Schema): + """Schema for validating military audit PATCH requests.""" + + militaryStatus = String( + required=True, allow_none=False, validate=OneOf(['approved', 'declined']) + ) + militaryStatusNote = String(required=False, allow_none=False) + + +MILITARY_AUDIT_ENDPOINT_RESOURCE = '/v1/compacts/{compact}/providers/{providerId}/militaryAudit' + + +@api_handler +@authorize_compact_level_only_action(action=CCPermissionsAction.ADMIN) +def military_audit_handler(event: dict, context: LambdaContext) -> dict: + """ + Handle military audit requests from compact admins. + + This endpoint allows compact admins to approve or decline military documentation + uploaded by providers. The audit result is stored on both the provider record + and the latest military affiliation record. + + :param event: API Gateway event + :param context: Lambda context + :return: Success message + """ + with logger.append_context_keys(aws_request=context.aws_request_id): + logger.info('Processing military audit request') + + # Extract path parameters + compact = event['pathParameters']['compact'] + provider_id = to_uuid(event['pathParameters']['providerId'], 'Invalid providerId provided') + + # Get the cognito sub of the caller for tracing + cognito_sub = event['requestContext']['authorizer']['claims']['sub'] + + with logger.append_context_keys( + compact=compact, provider_id=str(provider_id), cognito_sub=cognito_sub + ): + # Parse and validate request body + try: + body = json.loads(event['body']) + validated_body = MilitaryAuditRequestSchema().load(body) + except json.JSONDecodeError as e: + raise CCInvalidRequestException('Invalid JSON in request body') from e + except ValidationError as e: + raise CCInvalidRequestException(f'Invalid request body: {e.messages}') from e + + military_status_str = validated_body['militaryStatus'] + military_status_note = validated_body.get('militaryStatusNote') + + # Convert string to enum + military_status = MilitaryAuditStatus(military_status_str) + + logger.info( + 'Processing military audit', + military_status=military_status_str, + ) + + # Update provider and military affiliation records + config.data_client.process_military_audit( + compact=compact, + provider_id=provider_id, + military_status=military_status, + military_status_note=military_status_note, + ) + + # Publish event for notification + config.event_bus_client.publish_military_audit_event( + source='org.compactconnect.provider-data', + compact=compact, + provider_id=provider_id, + audit_result=military_status_str, + audit_note=military_status_note, + ) + + logger.info('Military audit processed successfully') + + return {'message': 'OK'} + diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_military_audit.py b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_military_audit.py new file mode 100644 index 000000000..fc8e03f9e --- /dev/null +++ b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_military_audit.py @@ -0,0 +1,212 @@ +import json +from datetime import datetime +from unittest.mock import patch + +from moto import mock_aws + +from common_test.test_constants import ( + DEFAULT_COMPACT, + DEFAULT_DATE_OF_UPDATE_TIMESTAMP, +) + +from .. import TstFunction + +MILITARY_AUDIT_ENDPOINT_RESOURCE = '/v1/compacts/{compact}/providers/{providerId}/militaryAudit' + + +@mock_aws +@patch('cc_common.config._Config.current_standard_datetime', datetime.fromisoformat(DEFAULT_DATE_OF_UPDATE_TIMESTAMP)) +class TestMilitaryAudit(TstFunction): + """Test suite for military audit endpoint.""" + + def _when_testing_military_audit(self, military_status: str, military_status_note: str | None = None): + """Set up test data and generate test event for military audit.""" + # Create provider and military affiliation records + test_provider = self.test_data_generator.put_default_provider_record_in_provider_table() + self.test_data_generator.put_default_military_affiliation_in_provider_table() + + body = {'militaryStatus': military_status} + if military_status_note: + body['militaryStatusNote'] = military_status_note + + test_event = self.test_data_generator.generate_test_api_event( + scope_override=f'openid email {DEFAULT_COMPACT}/admin', + value_overrides={ + 'httpMethod': 'PATCH', + 'resource': MILITARY_AUDIT_ENDPOINT_RESOURCE, + 'pathParameters': { + 'compact': test_provider.compact, + 'providerId': str(test_provider.providerId), + }, + 'body': json.dumps(body), + }, + ) + return test_event, test_provider + + def test_military_audit_approved_returns_ok(self): + """Test that approving military audit returns OK message.""" + from handlers.military_audit import military_audit_handler + + event, _ = self._when_testing_military_audit('approved') + + response = military_audit_handler(event, self.mock_context) + + self.assertEqual(200, response['statusCode'], msg=json.loads(response['body'])) + response_body = json.loads(response['body']) + self.assertEqual({'message': 'OK'}, response_body) + + def test_military_audit_declined_with_note_returns_ok(self): + """Test that declining military audit with a note returns OK message.""" + from handlers.military_audit import military_audit_handler + + event, _ = self._when_testing_military_audit('declined', 'Documentation was unclear') + + response = military_audit_handler(event, self.mock_context) + + self.assertEqual(200, response['statusCode'], msg=json.loads(response['body'])) + response_body = json.loads(response['body']) + self.assertEqual({'message': 'OK'}, response_body) + + def test_military_audit_updates_provider_record(self): + """Test that military audit updates the provider record with audit status.""" + from cc_common.data_model.schema.provider import ProviderData + from handlers.military_audit import military_audit_handler + + event, test_provider = self._when_testing_military_audit('approved') + + response = military_audit_handler(event, self.mock_context) + self.assertEqual(200, response['statusCode'], msg=json.loads(response['body'])) + + # Verify provider record was updated + updated_provider_record = self._provider_table.get_item( + Key={ + 'pk': f'{test_provider.compact}#PROVIDER#{test_provider.providerId}', + 'sk': f'{test_provider.compact}#PROVIDER', + } + )['Item'] + + loaded_provider = ProviderData.from_database_record(updated_provider_record) + + self.assertEqual('approved', loaded_provider.militaryStatus) + self.assertEqual('', loaded_provider.militaryStatusNote) + + def test_military_audit_declined_updates_with_note(self): + """Test that declining military audit updates provider and affiliation with note.""" + from cc_common.data_model.schema.provider import ProviderData + from handlers.military_audit import military_audit_handler + + event, test_provider = self._when_testing_military_audit('declined', 'Invalid documentation') + + response = military_audit_handler(event, self.mock_context) + self.assertEqual(200, response['statusCode'], msg=json.loads(response['body'])) + + # Verify provider record was updated + updated_provider_record = self._provider_table.get_item( + Key={ + 'pk': f'{test_provider.compact}#PROVIDER#{test_provider.providerId}', + 'sk': f'{test_provider.compact}#PROVIDER', + } + )['Item'] + + loaded_provider = ProviderData.from_database_record(updated_provider_record) + + self.assertEqual('declined', loaded_provider.militaryStatus) + self.assertEqual('Invalid documentation', loaded_provider.militaryStatusNote) + + def test_military_audit_invalid_status_returns_400(self): + """Test that an invalid military status returns 400 error.""" + from handlers.military_audit import military_audit_handler + + test_provider = self.test_data_generator.put_default_provider_record_in_provider_table() + self.test_data_generator.put_default_military_affiliation_in_provider_table() + + test_event = self.test_data_generator.generate_test_api_event( + scope_override=f'openid email {DEFAULT_COMPACT}/admin', + value_overrides={ + 'httpMethod': 'PATCH', + 'resource': MILITARY_AUDIT_ENDPOINT_RESOURCE, + 'pathParameters': { + 'compact': test_provider.compact, + 'providerId': str(test_provider.providerId), + }, + 'body': json.dumps({'militaryStatus': 'invalid_status'}), + }, + ) + + response = military_audit_handler(test_event, self.mock_context) + + self.assertEqual(400, response['statusCode']) + + def test_military_audit_missing_status_returns_400(self): + """Test that missing military status returns 400 error.""" + from handlers.military_audit import military_audit_handler + + test_provider = self.test_data_generator.put_default_provider_record_in_provider_table() + self.test_data_generator.put_default_military_affiliation_in_provider_table() + + test_event = self.test_data_generator.generate_test_api_event( + scope_override=f'openid email {DEFAULT_COMPACT}/admin', + value_overrides={ + 'httpMethod': 'PATCH', + 'resource': MILITARY_AUDIT_ENDPOINT_RESOURCE, + 'pathParameters': { + 'compact': test_provider.compact, + 'providerId': str(test_provider.providerId), + }, + 'body': json.dumps({}), + }, + ) + + response = military_audit_handler(test_event, self.mock_context) + + self.assertEqual(400, response['statusCode']) + + def test_military_audit_no_affiliation_returns_404(self): + """Test that audit fails when provider has no military affiliation records.""" + from handlers.military_audit import military_audit_handler + + # Only create provider, no military affiliation + test_provider = self.test_data_generator.put_default_provider_record_in_provider_table() + + test_event = self.test_data_generator.generate_test_api_event( + scope_override=f'openid email {DEFAULT_COMPACT}/admin', + value_overrides={ + 'httpMethod': 'PATCH', + 'resource': MILITARY_AUDIT_ENDPOINT_RESOURCE, + 'pathParameters': { + 'compact': test_provider.compact, + 'providerId': str(test_provider.providerId), + }, + 'body': json.dumps({'militaryStatus': 'approved'}), + }, + ) + + response = military_audit_handler(test_event, self.mock_context) + + self.assertEqual(404, response['statusCode']) + + def test_military_audit_unauthorized_returns_403(self): + """Test that non-admin users receive 403 Forbidden.""" + from handlers.military_audit import military_audit_handler + + test_provider = self.test_data_generator.put_default_provider_record_in_provider_table() + self.test_data_generator.put_default_military_affiliation_in_provider_table() + + # Use a non-admin scope + test_event = self.test_data_generator.generate_test_api_event( + scope_override=f'openid email {DEFAULT_COMPACT}/readGeneral', + value_overrides={ + 'httpMethod': 'PATCH', + 'resource': MILITARY_AUDIT_ENDPOINT_RESOURCE, + 'pathParameters': { + 'compact': test_provider.compact, + 'providerId': str(test_provider.providerId), + }, + 'body': json.dumps({'militaryStatus': 'approved'}), + }, + ) + + response = military_audit_handler(test_event, self.mock_context) + + self.assertEqual(403, response['statusCode']) + From 0bc639e73256d1e72fc6f9a3180252bf679d842c Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Wed, 17 Dec 2025 17:02:49 -0600 Subject: [PATCH 03/53] Add initial implementation for notification event handler --- .../data_model/schema/data_event/api.py | 12 +- .../common/cc_common/email_service_client.py | 48 ++++++++ .../common/cc_common/event_state_client.py | 4 +- .../handlers/military_audit_events.py | 99 +++++++++++++++ .../function/test_military_audit_events.py | 113 ++++++++++++++++++ 5 files changed, 274 insertions(+), 2 deletions(-) create mode 100644 backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py create mode 100644 backend/compact-connect/lambdas/python/data-events/tests/function/test_military_audit_events.py diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/data_event/api.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/data_event/api.py index ff73f21f3..af32a6927 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/data_event/api.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/data_event/api.py @@ -5,7 +5,7 @@ Jurisdiction, ) from marshmallow.fields import UUID, Date, DateTime, Email, List, Nested, String -from marshmallow.validate import Length +from marshmallow.validate import Length, OneOf class PrivilegeEventPrivilegeSchema(ForgivingSchema): @@ -84,3 +84,13 @@ class PrivilegeRevertDetailSchema(DataEventDetailBaseSchema): startTime = DateTime(required=True, allow_none=False) endTime = DateTime(required=True, allow_none=False) rollbackExecutionName = String(required=True, allow_none=False) + + +class MilitaryAuditEventDetailSchema(ForgivingSchema): + """Schema for military audit event details.""" + + compact = Compact(required=True, allow_none=False) + providerId = UUID(required=True, allow_none=False) + auditResult = String(required=True, allow_none=False, validate=OneOf(['approved', 'declined'])) + auditNote = String(required=False, allow_none=False) + eventTime = DateTime(required=True, allow_none=False) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/email_service_client.py b/backend/compact-connect/lambdas/python/common/cc_common/email_service_client.py index 0f1c150f7..d655c06b3 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/email_service_client.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/email_service_client.py @@ -760,3 +760,51 @@ def send_privilege_investigation_closed_state_notification_email( }, } return self._invoke_lambda(payload) + + def send_military_audit_approved_notification( + self, + *, + compact: str, + provider_email: str, + ) -> dict[str, str]: + """ + Send a military audit approved notification email to a provider. + + :param compact: Compact name + :param provider_email: Email address of the provider + :return: Response from the email notification service + """ + payload = { + 'compact': compact, + 'template': 'militaryAuditApprovedNotification', + 'recipientType': 'SPECIFIC', + 'specificEmails': [provider_email], + 'templateVariables': {}, + } + return self._invoke_lambda(payload) + + def send_military_audit_declined_notification( + self, + *, + compact: str, + provider_email: str, + audit_note: str, + ) -> dict[str, str]: + """ + Send a military audit declined notification email to a provider. + + :param compact: Compact name + :param provider_email: Email address of the provider + :param audit_note: Note from the admin explaining the decline + :return: Response from the email notification service + """ + payload = { + 'compact': compact, + 'template': 'militaryAuditDeclinedNotification', + 'recipientType': 'SPECIFIC', + 'specificEmails': [provider_email], + 'templateVariables': { + 'auditNote': audit_note, + }, + } + return self._invoke_lambda(payload) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/event_state_client.py b/backend/compact-connect/lambdas/python/common/cc_common/event_state_client.py index 12e929ef8..6849357b9 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/event_state_client.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/event_state_client.py @@ -21,12 +21,14 @@ class NotificationStatus(StrEnum): class EventType(StrEnum): - """Enum for encumbrance event types.""" + """Enum for event types that trigger notifications.""" LICENSE_ENCUMBRANCE = 'license.encumbrance' LICENSE_ENCUMBRANCE_LIFTED = 'license.encumbranceLifted' PRIVILEGE_ENCUMBRANCE = 'privilege.encumbrance' PRIVILEGE_ENCUMBRANCE_LIFTED = 'privilege.encumbranceLifted' + MILITARY_AUDIT_APPROVED = 'militaryAffiliation.auditApproved' + MILITARY_AUDIT_DECLINED = 'militaryAffiliation.auditDeclined' class EventStateClient: diff --git a/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py b/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py new file mode 100644 index 000000000..f6e929bae --- /dev/null +++ b/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py @@ -0,0 +1,99 @@ +from uuid import UUID + +from cc_common.config import config, logger +from cc_common.data_model.schema.data_event.api import MilitaryAuditEventDetailSchema +from cc_common.event_state_client import EventType, NotificationTracker, RecipientType +from cc_common.utils import sqs_handler_with_notification_tracking +from cc_common.exceptions import CCInternalException + + +@sqs_handler_with_notification_tracking +def military_audit_notification_listener(message: dict, tracker: NotificationTracker): # noqa: ARG001 + """ + Handle military audit events and send notifications to providers. + + This listener is triggered by EventBridge events when a compact admin + approves or declines a provider's military documentation. + + :param message: The SQS message containing the EventBridge event + :param tracker: NotificationTracker for idempotency + """ + logger.info('Processing military audit notification event') + + # Validate and extract event detail + detail = message.get('detail', {}) + schema = MilitaryAuditEventDetailSchema() + validated_detail = schema.load(detail) + + compact = validated_detail['compact'] + provider_id = validated_detail['providerId'] + audit_result = validated_detail['auditResult'] + audit_note = validated_detail.get('auditNote', '') + event_time = validated_detail['eventTime'].isoformat() + + with logger.append_context_keys( + compact=compact, + provider_id=str(provider_id), + audit_result=audit_result, + ): + logger.info('Processing military audit notification') + + # Get provider records to find registered email + try: + provider_records = config.data_client.get_provider_user_records( + compact=compact, + provider_id=UUID(str(provider_id)), + ) + provider_record = provider_records.get_provider_record() + except Exception as e: + logger.error('Failed to retrieve provider records for notification', exception=str(e)) + raise + + provider_email = provider_record.compactConnectRegisteredEmailAddress + + if not provider_email: + message = 'Provider email not found in system' + logger.error(message) + raise CCInternalException(message) + + # Check if we should send the notification (idempotency) + if not tracker.should_send_provider_notification(): + logger.info('Skipping provider notification (already sent successfully)') + return + + # Determine event type and send appropriate notification + event_type = EventType.MILITARY_AUDIT_APPROVED if audit_result == 'approved' else EventType.MILITARY_AUDIT_DECLINED + + try: + if audit_result == 'approved': + logger.info('Sending military audit approved notification to provider') + config.email_service_client.send_military_audit_approved_notification( + compact=compact, + provider_email=provider_email, + ) + else: + logger.info('Sending military audit declined notification to provider') + config.email_service_client.send_military_audit_declined_notification( + compact=compact, + provider_email=provider_email, + audit_note=audit_note, + ) + + logger.info('Successfully sent military audit notification to provider') + tracker.record_success( + recipient_type=RecipientType.PROVIDER, + provider_id=provider_id, + event_type=event_type, + event_time=event_time, + ) + except Exception as e: + logger.error('Failed to send military audit notification', exception=str(e)) + tracker.record_failure( + recipient_type=RecipientType.PROVIDER, + provider_id=provider_id, + event_type=event_type, + event_time=event_time, + error_message=str(e), + ) + raise + diff --git a/backend/compact-connect/lambdas/python/data-events/tests/function/test_military_audit_events.py b/backend/compact-connect/lambdas/python/data-events/tests/function/test_military_audit_events.py new file mode 100644 index 000000000..338a2e409 --- /dev/null +++ b/backend/compact-connect/lambdas/python/data-events/tests/function/test_military_audit_events.py @@ -0,0 +1,113 @@ +import json +import uuid +from datetime import datetime +from unittest.mock import patch + +from common_test.test_constants import ( + DEFAULT_COMPACT, + DEFAULT_DATE_OF_UPDATE_TIMESTAMP, + DEFAULT_PROVIDER_ID, +) +from moto import mock_aws + +from . import TstFunction + + +@mock_aws +@patch('cc_common.config._Config.current_standard_datetime', datetime.fromisoformat(DEFAULT_DATE_OF_UPDATE_TIMESTAMP)) +class TestMilitaryAuditEvents(TstFunction): + """Test suite for military audit event handlers.""" + + def _generate_military_audit_message(self, audit_result: str, audit_note: str | None = None): + """Generate a test EventBridge message for military audit events.""" + message = { + 'detail': { + 'compact': DEFAULT_COMPACT, + 'providerId': DEFAULT_PROVIDER_ID, + 'auditResult': audit_result, + 'eventTime': DEFAULT_DATE_OF_UPDATE_TIMESTAMP, + } + } + if audit_note: + message['detail']['auditNote'] = audit_note + return message + + def _create_sqs_event(self, message): + """Create a proper SQS event structure with the message in the body.""" + return {'Records': [{'messageId': str(uuid.uuid4()), 'body': json.dumps(message)}]} + + @patch('cc_common.email_service_client.EmailServiceClient.send_military_audit_approved_notification') + def test_military_audit_approved_sends_notification_to_registered_provider(self, mock_send_email): + """Test that approved military audit sends notification to registered provider.""" + from handlers.military_audit_events import military_audit_notification_listener + + # Set up test data - provider must be registered (have email) + self.test_data_generator.put_default_provider_record_in_provider_table( + value_overrides={'compactConnectRegisteredEmailAddress': 'provider@example.com'} + ) + + message = self._generate_military_audit_message('approved') + event = self._create_sqs_event(message) + + # Execute the handler + result = military_audit_notification_listener(event, self.mock_context) + + # Should succeed with no batch failures + self.assertEqual({'batchItemFailures': []}, result) + + # Verify email was sent + mock_send_email.assert_called_once_with( + compact=DEFAULT_COMPACT, + provider_email='provider@example.com', + ) + + @patch('cc_common.email_service_client.EmailServiceClient.send_military_audit_declined_notification') + def test_military_audit_declined_sends_notification_with_note_to_registered_provider(self, mock_send_email): + """Test that declined military audit sends notification with note to registered provider.""" + from handlers.military_audit_events import military_audit_notification_listener + + # Set up test data - provider must be registered (have email) + self.test_data_generator.put_default_provider_record_in_provider_table( + value_overrides={'compactConnectRegisteredEmailAddress': 'provider@example.com'} + ) + + audit_note = 'Documentation was unclear and needs to be resubmitted.' + message = self._generate_military_audit_message('declined', audit_note) + event = self._create_sqs_event(message) + + # Execute the handler + result = military_audit_notification_listener(event, self.mock_context) + + # Should succeed with no batch failures + self.assertEqual({'batchItemFailures': []}, result) + + # Verify email was sent with audit note + mock_send_email.assert_called_once_with( + compact=DEFAULT_COMPACT, + provider_email='provider@example.com', + audit_note=audit_note, + ) + + @patch('cc_common.email_service_client.EmailServiceClient.send_military_audit_approved_notification') + def test_military_audit_records_failure_on_email_error(self, mock_send_email): + """Test that email failures are recorded properly in event state table.""" + from handlers.military_audit_events import military_audit_notification_listener + + # Set up test data - provider registered + self.test_data_generator.put_default_provider_record_in_provider_table( + value_overrides={'compactConnectRegisteredEmailAddress': 'provider@example.com'} + ) + + # Simulate email send failure + mock_send_email.side_effect = Exception('Email service unavailable') + + message = self._generate_military_audit_message('approved') + event = self._create_sqs_event(message) + + # Execute the handler - should return item failure + result = military_audit_notification_listener(event, self.mock_context) + + # Should have one batch item failure + self.assertEqual(1, len(result['batchItemFailures'])) + + From 05b4808cc780251fa4109bfa666784d3d208abea Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Mon, 22 Dec 2025 09:58:28 -0700 Subject: [PATCH 04/53] Adding provider update record with previous status --- .../cc_common/data_model/data_client.py | 28 ++++- .../cc_common/data_model/schema/common.py | 1 + .../test_handlers/test_military_audit.py | 116 ++++++++++-------- 3 files changed, 91 insertions(+), 54 deletions(-) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py index 619258bf1..616abf59a 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py @@ -826,6 +826,26 @@ def process_military_audit( # Prepare the note value (empty string if not provided) note_value = military_status_note or '' + # Capture previous state before updating + previous_provider_state = provider_record.to_dict() + + # Create provider update record to track the audit + now = config.current_standard_datetime + provider_update_record = ProviderUpdateData.create_new( + { + 'type': ProviderRecordType.PROVIDER_UPDATE, + 'updateType': UpdateCategory.MILITARY_AUDIT, + 'providerId': provider_id, + 'compact': compact, + 'previous': previous_provider_state, + 'createDate': now, + 'updatedValues': { + 'militaryStatus': military_status.value, + 'militaryStatusNote': note_value, + }, + } + ) + # Update provider record with military status provider_record.update( { @@ -834,7 +854,6 @@ def process_military_audit( } ) - #TODO - create provider update record to track any previous values # Execute both updates in a transaction self.config.dynamodb_client.transact_write_items( @@ -846,6 +865,13 @@ def process_military_audit( 'Item': TypeSerializer().serialize(provider_record.serialize_to_database_record())['M'], } }, + # Create provider update record + { + 'Put': { + 'TableName': self.config.provider_table_name, + 'Item': TypeSerializer().serialize(provider_update_record.serialize_to_database_record())['M'], + } + }, ] ) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py index 5039a0d6b..f1ece61f8 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py @@ -306,6 +306,7 @@ class UpdateCategory(CCEnum): EXPIRATION = 'expiration' ISSUANCE = 'issuance' RENEWAL = 'renewal' + MILITARY_AUDIT = 'militaryAudit' ENCUMBRANCE = 'encumbrance' INVESTIGATION = 'investigation' CLOSING_INVESTIGATION = 'closingInvestigation' diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_military_audit.py b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_military_audit.py index fc8e03f9e..aa0ae2a2a 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_military_audit.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_military_audit.py @@ -19,11 +19,12 @@ class TestMilitaryAudit(TstFunction): """Test suite for military audit endpoint.""" - def _when_testing_military_audit(self, military_status: str, military_status_note: str | None = None): + def _when_testing_military_audit(self, military_status: str, military_status_note: str | None = None, test_provider = None): """Set up test data and generate test event for military audit.""" # Create provider and military affiliation records - test_provider = self.test_data_generator.put_default_provider_record_in_provider_table() - self.test_data_generator.put_default_military_affiliation_in_provider_table() + if test_provider is None: + test_provider = self.test_data_generator.put_default_provider_record_in_provider_table() + self.test_data_generator.put_default_military_affiliation_in_provider_table() body = {'militaryStatus': military_status} if military_status_note: @@ -69,7 +70,6 @@ def test_military_audit_declined_with_note_returns_ok(self): def test_military_audit_updates_provider_record(self): """Test that military audit updates the provider record with audit status.""" - from cc_common.data_model.schema.provider import ProviderData from handlers.military_audit import military_audit_handler event, test_provider = self._when_testing_military_audit('approved') @@ -78,21 +78,15 @@ def test_military_audit_updates_provider_record(self): self.assertEqual(200, response['statusCode'], msg=json.loads(response['body'])) # Verify provider record was updated - updated_provider_record = self._provider_table.get_item( - Key={ - 'pk': f'{test_provider.compact}#PROVIDER#{test_provider.providerId}', - 'sk': f'{test_provider.compact}#PROVIDER', - } - )['Item'] + updated_provider_record = self.config.data_client.get_provider_top_level_record( + compact=test_provider.compact, provider_id=test_provider.providerId) - loaded_provider = ProviderData.from_database_record(updated_provider_record) - self.assertEqual('approved', loaded_provider.militaryStatus) - self.assertEqual('', loaded_provider.militaryStatusNote) + self.assertEqual('approved', updated_provider_record.militaryStatus) + self.assertEqual('', updated_provider_record.militaryStatusNote) def test_military_audit_declined_updates_with_note(self): """Test that declining military audit updates provider and affiliation with note.""" - from cc_common.data_model.schema.provider import ProviderData from handlers.military_audit import military_audit_handler event, test_provider = self._when_testing_military_audit('declined', 'Invalid documentation') @@ -101,41 +95,24 @@ def test_military_audit_declined_updates_with_note(self): self.assertEqual(200, response['statusCode'], msg=json.loads(response['body'])) # Verify provider record was updated - updated_provider_record = self._provider_table.get_item( - Key={ - 'pk': f'{test_provider.compact}#PROVIDER#{test_provider.providerId}', - 'sk': f'{test_provider.compact}#PROVIDER', - } - )['Item'] - - loaded_provider = ProviderData.from_database_record(updated_provider_record) + updated_provider_record = self.config.data_client.get_provider_top_level_record( + compact=test_provider.compact, provider_id=test_provider.providerId) - self.assertEqual('declined', loaded_provider.militaryStatus) - self.assertEqual('Invalid documentation', loaded_provider.militaryStatusNote) + self.assertEqual('declined', updated_provider_record.militaryStatus) + self.assertEqual('Invalid documentation', updated_provider_record.militaryStatusNote) def test_military_audit_invalid_status_returns_400(self): """Test that an invalid military status returns 400 error.""" from handlers.military_audit import military_audit_handler - test_provider = self.test_data_generator.put_default_provider_record_in_provider_table() - self.test_data_generator.put_default_military_affiliation_in_provider_table() - - test_event = self.test_data_generator.generate_test_api_event( - scope_override=f'openid email {DEFAULT_COMPACT}/admin', - value_overrides={ - 'httpMethod': 'PATCH', - 'resource': MILITARY_AUDIT_ENDPOINT_RESOURCE, - 'pathParameters': { - 'compact': test_provider.compact, - 'providerId': str(test_provider.providerId), - }, - 'body': json.dumps({'militaryStatus': 'invalid_status'}), - }, - ) + event, _ = self._when_testing_military_audit('foo', + 'Documentation verified',) - response = military_audit_handler(test_event, self.mock_context) + response = military_audit_handler(event, self.mock_context) self.assertEqual(400, response['statusCode']) + self.assertEqual({'message': "Invalid request body: {'militaryStatus': ['Must be one of: " + "approved, declined.']}"}, json.loads(response['body'])) def test_military_audit_missing_status_returns_400(self): """Test that missing military status returns 400 error.""" @@ -168,20 +145,11 @@ def test_military_audit_no_affiliation_returns_404(self): # Only create provider, no military affiliation test_provider = self.test_data_generator.put_default_provider_record_in_provider_table() - test_event = self.test_data_generator.generate_test_api_event( - scope_override=f'openid email {DEFAULT_COMPACT}/admin', - value_overrides={ - 'httpMethod': 'PATCH', - 'resource': MILITARY_AUDIT_ENDPOINT_RESOURCE, - 'pathParameters': { - 'compact': test_provider.compact, - 'providerId': str(test_provider.providerId), - }, - 'body': json.dumps({'militaryStatus': 'approved'}), - }, - ) + event, _ = self._when_testing_military_audit('approved', + 'Documentation verified', + test_provider=test_provider) - response = military_audit_handler(test_event, self.mock_context) + response = military_audit_handler(event, self.mock_context) self.assertEqual(404, response['statusCode']) @@ -210,3 +178,45 @@ def test_military_audit_unauthorized_returns_403(self): self.assertEqual(403, response['statusCode']) + def test_military_audit_creates_provider_update_record(self): + """Test that military audit creates a provider update record with expected values.""" + from cc_common.data_model.schema.common import UpdateCategory + from cc_common.data_model.schema.provider import ProviderUpdateData + from handlers.military_audit import military_audit_handler + + # Create provider with initial military status + test_provider = self.test_data_generator.put_default_provider_record_in_provider_table( + value_overrides={'militaryStatus': 'tentative', 'militaryStatusNote': ''} + ) + self.test_data_generator.put_default_military_affiliation_in_provider_table() + + event, _ = self._when_testing_military_audit('approved', + 'Documentation verified', + test_provider=test_provider) + + response = military_audit_handler(event, self.mock_context) + self.assertEqual(200, response['statusCode'], msg=json.loads(response['body'])) + + # Query provider update records + stored_provider_update_records = ( + self.test_data_generator.query_provider_update_records_for_given_record_from_database(test_provider) + ) + + # Verify exactly one update record was created + self.assertEqual(1, len(stored_provider_update_records)) + + # Verify the update record contents + update_data = ProviderUpdateData.from_database_record(stored_provider_update_records[0]) + self.assertEqual(UpdateCategory.MILITARY_AUDIT, update_data.updateType) + self.assertEqual(test_provider.providerId, update_data.providerId) + self.assertEqual(test_provider.compact, update_data.compact) + + # Verify previous state was captured + self.assertIsNotNone(update_data.previous) + self.assertEqual('tentative', update_data.previous.get('militaryStatus')) + self.assertEqual('', update_data.previous.get('militaryStatusNote')) + + # Verify updated values + self.assertEqual('approved', update_data.updatedValues['militaryStatus']) + self.assertEqual('Documentation verified', update_data.updatedValues['militaryStatusNote']) + From d0cf2f320039482c6552484c4eadca07f814db38 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Mon, 22 Dec 2025 10:31:31 -0700 Subject: [PATCH 05/53] setting default military status fields in api response rather than database schema class --- .../cc_common/data_model/provider_record_util.py | 7 +++++++ .../cc_common/data_model/schema/provider/api.py | 3 +-- .../cc_common/data_model/schema/provider/record.py | 11 +---------- .../tests/resources/api/provider-detail-response.json | 2 ++ .../tests/function/test_handlers/test_providers.py | 2 ++ .../function/test_handlers/test_public_lookup.py | 2 ++ 6 files changed, 15 insertions(+), 12 deletions(-) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py index cac637925..5e5d2ecc1 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py @@ -15,6 +15,7 @@ AdverseActionAgainstEnum, CompactEligibilityStatus, HomeJurisdictionChangeStatusEnum, + MilitaryAuditStatus, PrivilegeEncumberedStatusEnum, UpdateCategory, ) @@ -929,4 +930,10 @@ def generate_api_response_object(self) -> dict: provider['privileges'] = privileges provider['militaryAffiliations'] = military_affiliations + # Set default values for military audit status fields if not present + if 'militaryStatus' not in provider: + provider['militaryStatus'] = MilitaryAuditStatus.NOT_APPLICABLE.value + if 'militaryStatusNote' not in provider: + provider['militaryStatusNote'] = '' + return provider diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/api.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/api.py index d8ebd84a2..15a4f98a1 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/api.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/api.py @@ -184,9 +184,8 @@ class ProviderGeneralResponseSchema(ForgivingSchema): privileges = List(Nested(PrivilegeGeneralResponseSchema(), required=False, allow_none=False)) militaryAffiliations = List(Nested(MilitaryAffiliationGeneralResponseSchema(), required=False, allow_none=False)) - # Military audit status fields + # Military audit status field (note is only available in readPrivate response) militaryStatus = MilitaryAuditStatusField(required=False, allow_none=False) - militaryStatusNote = String(required=False, allow_none=False) class ProviderPublicResponseSchema(ForgivingSchema): diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py index 18980408d..0a59c7685 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py @@ -99,16 +99,7 @@ class ProviderRecordSchema(BaseRecordSchema): def _calculate_statuses(self, in_data, **_kwargs): """Determine the statuses of the record based on the expiration date""" in_data = self._calculate_license_status(in_data) - in_data = self._calculate_compact_eligibility(in_data) - return self._set_military_status_defaults(in_data) - - def _set_military_status_defaults(self, in_data, **_kwargs): - """Set default values for military audit status fields if not present""" - if 'militaryStatus' not in in_data: - in_data['militaryStatus'] = MilitaryAuditStatus.NOT_APPLICABLE - if 'militaryStatusNote' not in in_data: - in_data['militaryStatusNote'] = '' - return in_data + return self._calculate_compact_eligibility(in_data) def _calculate_license_status(self, in_data, **_kwargs): """Determine the status of the license based on the expiration date""" diff --git a/backend/compact-connect/lambdas/python/common/tests/resources/api/provider-detail-response.json b/backend/compact-connect/lambdas/python/common/tests/resources/api/provider-detail-response.json index 47ab5f47d..0e671526a 100644 --- a/backend/compact-connect/lambdas/python/common/tests/resources/api/provider-detail-response.json +++ b/backend/compact-connect/lambdas/python/common/tests/resources/api/provider-detail-response.json @@ -3,6 +3,8 @@ "providerId": "89a6377e-c3a5-40e5-bca5-317ec854c570", "npi": "0608337260", "ssnLastFour": "1234", + "militaryStatus": "notApplicable", + "militaryStatusNote": "", "givenName": "Björk", "middleName": "Gunnar", "familyName": "Guðmundsdóttir", diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_providers.py b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_providers.py index cb5baab09..c137017e3 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_providers.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_providers.py @@ -373,6 +373,8 @@ def test_get_provider_returns_expected_general_response_when_caller_does_not_hav expected_provider['militaryAffiliations'][0].pop('documentKeys') del expected_provider['licenses'][0]['ssnLastFour'] del expected_provider['licenses'][0]['dateOfBirth'] + # remove the military status note field, only viewable with read private scope + del expected_provider['militaryStatusNote'] self._when_testing_get_provider_response_based_on_read_access( scopes='openid email aslp/readGeneral', expected_provider=expected_provider diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_public_lookup.py b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_public_lookup.py index 11418ce5b..1c8884811 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_public_lookup.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_public_lookup.py @@ -454,6 +454,8 @@ def test_public_get_provider_response_with_expected_fields_filtered(self): expected_provider.pop('dateOfExpiration') expected_provider.pop('jurisdictionUploadedLicenseStatus') expected_provider.pop('jurisdictionUploadedCompactEligibility') + expected_provider.pop('militaryStatus') + expected_provider.pop('militaryStatusNote') self.assertEqual(expected_provider, provider_data) From 32127003926713a046958f8d465d145e72472733 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Mon, 22 Dec 2025 16:50:54 -0700 Subject: [PATCH 06/53] Setting provider military status to tentative when file is uploaded --- .../cc_common/data_model/data_client.py | 119 +++++++++---- .../data_model/provider_record_util.py | 15 ++ .../cc_common/data_model/schema/common.py | 2 + .../cc_common/data_model/schema/fields.py | 2 +- .../schema/military_affiliation/api.py | 8 + .../data_model/schema/provider/record.py | 1 - .../handlers/military_audit_events.py | 7 +- .../function/test_military_audit_events.py | 2 - .../handlers/military_audit.py | 24 +-- .../test_handlers/test_military_audit.py | 35 ++-- .../test_handlers/test_provider_s3_events.py | 159 +++++++++++++++++- 11 files changed, 292 insertions(+), 82 deletions(-) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py index 616abf59a..dbec0cabe 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py @@ -670,59 +670,105 @@ def _get_all_military_affiliation_records_for_provider(self, compact: str, provi return [MilitaryAffiliationData.from_database_record(record) for record in military_affiliation_records] - def _get_military_affiliation_records_by_status( - self, compact: str, provider_id: str, status: MilitaryAffiliationStatus - ): - military_affiliation_records = self.config.provider_table.query( - KeyConditionExpression=Key('pk').eq(f'{compact}#PROVIDER#{provider_id}') - & Key('sk').begins_with( - f'{compact}#PROVIDER#military-affiliation#', - ), - FilterExpression=Attr('status').eq(status.value), - ).get('Items', []) - - schema = MilitaryAffiliationRecordSchema() - return [schema.load(record) for record in military_affiliation_records] - - def _get_active_military_affiliation_records(self, compact: str, provider_id: str): - return self._get_military_affiliation_records_by_status(compact, provider_id, MilitaryAffiliationStatus.ACTIVE) - - def _get_initializing_military_affiliation_records(self, compact: str, provider_id: str): - return self._get_military_affiliation_records_by_status( - compact, provider_id, MilitaryAffiliationStatus.INITIALIZING - ) - @logger_inject_kwargs(logger, 'compact', 'provider_id') def complete_military_affiliation_initialization(self, compact: str, provider_id: str): """ This method is called when the client has uploaded the document for a military affiliation record. It gets all records in an initializing state, sets the latest to active, and the rest to inactive for a - self-healing process. + self-healing process. Also updates the provider record's militaryStatus to TENTATIVE and creates a + provider update record to track this change. """ logger.info('Completing military affiliation initialization') - initializing_military_affiliation_records = self._get_initializing_military_affiliation_records( - compact, provider_id + provider_user_records = self.get_provider_user_records(compact=compact, provider_id=UUID(provider_id)) + + initializing_military_affiliation_records = provider_user_records.get_military_affiliation_records( + filter_condition=lambda record: record.status == MilitaryAffiliationStatus.INITIALIZING ) if not initializing_military_affiliation_records: - return + raise CCInternalException('No initializing military affiliation records found for provider') + # Find the latest military affiliation record by dateOfUpload latest_military_affiliation_record = max( - initializing_military_affiliation_records, key=lambda record: record['dateOfUpload'] + initializing_military_affiliation_records, key=lambda record: record.dateOfUpload ) - schema = MilitaryAffiliationRecordSchema() - with self.config.provider_table.batch_writer() as batch: - for record in initializing_military_affiliation_records: - if record['dateOfUpload'] == latest_military_affiliation_record['dateOfUpload']: - record['status'] = MilitaryAffiliationStatus.ACTIVE.value - else: - record['status'] = MilitaryAffiliationStatus.INACTIVE.value + # Get provider record and capture previous state + provider_record = provider_user_records.get_provider_record() + # Create provider update record to track the military file upload + now = config.current_standard_datetime + provider_update_record = ProviderUpdateData.create_new( + { + 'type': ProviderRecordType.PROVIDER_UPDATE, + 'updateType': UpdateCategory.MILITARY_FILE_UPLOAD, + 'providerId': UUID(provider_id), + 'compact': compact, + 'previous': provider_record.to_dict(), + 'createDate': now, + 'updatedValues': { + 'militaryStatus': MilitaryAuditStatus.TENTATIVE.value, + 'militaryStatusNote': '', + }, + } + ) - serialized_record = schema.dump(record) - batch.put_item(Item=serialized_record) + # Update provider record with militaryStatus set to TENTATIVE + provider_record.update( + { + 'militaryStatus': MilitaryAuditStatus.TENTATIVE.value, + 'militaryStatusNote': '', + } + ) + + # Build transaction items with provider and provider update record + transaction_items = [ + { + 'Put': { + 'TableName': self.config.provider_table_name, + 'Item': TypeSerializer().serialize(provider_record.serialize_to_database_record())['M'], + } + }, + { + 'Put': { + 'TableName': self.config.provider_table_name, + 'Item': TypeSerializer().serialize(provider_update_record.serialize_to_database_record())['M'], + } + }, + ] + + # Update all military affiliation records + for record in initializing_military_affiliation_records: + if record.dateOfUpload == latest_military_affiliation_record.dateOfUpload: + record.update({'status': MilitaryAffiliationStatus.ACTIVE.value}) + else: + record.update({'status': MilitaryAffiliationStatus.INACTIVE.value}) + + serialized_record = record.serialize_to_database_record() + transaction_items.append( + { + 'Put': { + 'TableName': self.config.provider_table_name, + 'Item': TypeSerializer().serialize(serialized_record)['M'], + } + } + ) + + # Execute transaction in batches if needed (DynamoDB limit is 100 items) + batch_size = 100 + while transaction_items: + batch = transaction_items[:batch_size] + transaction_items = transaction_items[batch_size:] + + try: + self.config.dynamodb_client.transact_write_items(TransactItems=batch) + logger.info('Successfully processed military affiliation initialization batch', batch_size=len(batch)) + except ClientError as e: + logger.error('Failed to process military affiliation initialization transaction', error=str(e)) + raise CCAwsServiceException('Failed to complete military affiliation initialization') from e + + logger.info('Successfully completed military affiliation initialization') @logger_inject_kwargs(logger, 'compact', 'provider_id', 'affiliation_type') def create_military_affiliation( @@ -854,7 +900,6 @@ def process_military_audit( } ) - # Execute both updates in a transaction self.config.dynamodb_client.transact_write_items( TransactItems=[ diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py index 5e5d2ecc1..f696d8366 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py @@ -771,6 +771,21 @@ def get_latest_military_affiliation_status(self) -> str | None: return latest_military_affiliation.status + def get_military_affiliation_records( + self, filter_condition: Callable[[MilitaryAffiliationData], bool] | None = None + ) -> list[MilitaryAffiliationData]: + """ + Get all military affiliation records for this provider. + :param filter_condition: An optional filter to apply to the military affiliation records + :return: The most recent military affiliation record if present, else None + """ + + return [ + record + for record in self._military_affiliation_records + if filter_condition is None or filter_condition(record) + ] + def get_latest_military_affiliation(self) -> MilitaryAffiliationData | None: """ Get the most recent military affiliation record for this provider. diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py index f1ece61f8..6b92c8216 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py @@ -292,6 +292,7 @@ class InvestigationAgainstEnum(StrEnum): PRIVILEGE = 'privilege' LICENSE = 'license' + class MilitaryAuditStatus(CCEnum): """Status of military documentation audit by compact admins.""" @@ -307,6 +308,7 @@ class UpdateCategory(CCEnum): ISSUANCE = 'issuance' RENEWAL = 'renewal' MILITARY_AUDIT = 'militaryAudit' + MILITARY_FILE_UPLOAD = 'militaryFileUpload' ENCUMBRANCE = 'encumbrance' INVESTIGATION = 'investigation' CLOSING_INVESTIGATION = 'closingInvestigation' diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py index a5d59503e..d1aa97415 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py @@ -12,10 +12,10 @@ InvestigationStatusEnum, LicenseDeactivatedStatusEnum, LicenseEncumberedStatusEnum, + MilitaryAuditStatus, PrivilegeEncumberedStatusEnum, UpdateCategory, ) -from cc_common.data_model.schema.common import MilitaryAuditStatus # This is a special value that is used to indicate that the provider's home jurisdiction is not known. # This can happen if a provider moves to a jurisdiction that is not part of the compact. diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/api.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/api.py index edf874d28..f10682878 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/api.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/api.py @@ -1,4 +1,5 @@ # ruff: noqa: N801, N815, ARG002 invalid-name unused-argument +from marshmallow import Schema from marshmallow.fields import Dict, List, Nested, Raw, String from marshmallow.validate import OneOf @@ -23,6 +24,13 @@ class PostMilitaryAffiliationResponseSchema(ForgivingSchema): ) +class MilitaryAuditRequestSchema(Schema): + """Schema for validating military audit PATCH requests.""" + + militaryStatus = String(required=True, allow_none=False, validate=OneOf(['approved', 'declined'])) + militaryStatusNote = String(required=False, allow_none=False) + + class MilitaryAffiliationGeneralResponseSchema(ForgivingSchema): """ Schema defining fields available to all staff users with only the 'readGeneral' permission. diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py index 0a59c7685..8bb127f2d 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py @@ -27,7 +27,6 @@ Set, UpdateType, ) -from cc_common.data_model.schema.common import MilitaryAuditStatus from cc_common.data_model.update_tier_enum import UpdateTierEnum diff --git a/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py b/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py index f6e929bae..4601624fb 100644 --- a/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py +++ b/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py @@ -3,8 +3,8 @@ from cc_common.config import config, logger from cc_common.data_model.schema.data_event.api import MilitaryAuditEventDetailSchema from cc_common.event_state_client import EventType, NotificationTracker, RecipientType -from cc_common.utils import sqs_handler_with_notification_tracking from cc_common.exceptions import CCInternalException +from cc_common.utils import sqs_handler_with_notification_tracking @sqs_handler_with_notification_tracking @@ -62,7 +62,9 @@ def military_audit_notification_listener(message: dict, tracker: NotificationTra return # Determine event type and send appropriate notification - event_type = EventType.MILITARY_AUDIT_APPROVED if audit_result == 'approved' else EventType.MILITARY_AUDIT_DECLINED + event_type = ( + EventType.MILITARY_AUDIT_APPROVED if audit_result == 'approved' else EventType.MILITARY_AUDIT_DECLINED + ) try: if audit_result == 'approved': @@ -96,4 +98,3 @@ def military_audit_notification_listener(message: dict, tracker: NotificationTra error_message=str(e), ) raise - diff --git a/backend/compact-connect/lambdas/python/data-events/tests/function/test_military_audit_events.py b/backend/compact-connect/lambdas/python/data-events/tests/function/test_military_audit_events.py index 338a2e409..32256520a 100644 --- a/backend/compact-connect/lambdas/python/data-events/tests/function/test_military_audit_events.py +++ b/backend/compact-connect/lambdas/python/data-events/tests/function/test_military_audit_events.py @@ -109,5 +109,3 @@ def test_military_audit_records_failure_on_email_error(self, mock_send_email): # Should have one batch item failure self.assertEqual(1, len(result['batchItemFailures'])) - - diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/military_audit.py b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/military_audit.py index b59ba65b8..6bec15cd2 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/military_audit.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/military_audit.py @@ -1,25 +1,12 @@ import json from aws_lambda_powertools.utilities.typing import LambdaContext -from marshmallow import Schema, ValidationError -from marshmallow.fields import String -from marshmallow.validate import OneOf - from cc_common.config import config, logger -from cc_common.data_model.schema.common import MilitaryAuditStatus +from cc_common.data_model.schema.common import CCPermissionsAction, MilitaryAuditStatus +from cc_common.data_model.schema.military_affiliation.api import MilitaryAuditRequestSchema from cc_common.exceptions import CCInvalidRequestException from cc_common.utils import api_handler, authorize_compact_level_only_action, to_uuid -from cc_common.data_model.schema.common import CCPermissionsAction - - -class MilitaryAuditRequestSchema(Schema): - """Schema for validating military audit PATCH requests.""" - - militaryStatus = String( - required=True, allow_none=False, validate=OneOf(['approved', 'declined']) - ) - militaryStatusNote = String(required=False, allow_none=False) - +from marshmallow import ValidationError MILITARY_AUDIT_ENDPOINT_RESOURCE = '/v1/compacts/{compact}/providers/{providerId}/militaryAudit' @@ -48,9 +35,7 @@ def military_audit_handler(event: dict, context: LambdaContext) -> dict: # Get the cognito sub of the caller for tracing cognito_sub = event['requestContext']['authorizer']['claims']['sub'] - with logger.append_context_keys( - compact=compact, provider_id=str(provider_id), cognito_sub=cognito_sub - ): + with logger.append_context_keys(compact=compact, provider_id=str(provider_id), cognito_sub=cognito_sub): # Parse and validate request body try: body = json.loads(event['body']) @@ -91,4 +76,3 @@ def military_audit_handler(event: dict, context: LambdaContext) -> dict: logger.info('Military audit processed successfully') return {'message': 'OK'} - diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_military_audit.py b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_military_audit.py index aa0ae2a2a..0c85b92cc 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_military_audit.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_military_audit.py @@ -2,12 +2,11 @@ from datetime import datetime from unittest.mock import patch -from moto import mock_aws - from common_test.test_constants import ( DEFAULT_COMPACT, DEFAULT_DATE_OF_UPDATE_TIMESTAMP, ) +from moto import mock_aws from .. import TstFunction @@ -19,7 +18,9 @@ class TestMilitaryAudit(TstFunction): """Test suite for military audit endpoint.""" - def _when_testing_military_audit(self, military_status: str, military_status_note: str | None = None, test_provider = None): + def _when_testing_military_audit( + self, military_status: str, military_status_note: str | None = None, test_provider=None + ): """Set up test data and generate test event for military audit.""" # Create provider and military affiliation records if test_provider is None: @@ -79,8 +80,8 @@ def test_military_audit_updates_provider_record(self): # Verify provider record was updated updated_provider_record = self.config.data_client.get_provider_top_level_record( - compact=test_provider.compact, provider_id=test_provider.providerId) - + compact=test_provider.compact, provider_id=test_provider.providerId + ) self.assertEqual('approved', updated_provider_record.militaryStatus) self.assertEqual('', updated_provider_record.militaryStatusNote) @@ -96,7 +97,8 @@ def test_military_audit_declined_updates_with_note(self): # Verify provider record was updated updated_provider_record = self.config.data_client.get_provider_top_level_record( - compact=test_provider.compact, provider_id=test_provider.providerId) + compact=test_provider.compact, provider_id=test_provider.providerId + ) self.assertEqual('declined', updated_provider_record.militaryStatus) self.assertEqual('Invalid documentation', updated_provider_record.militaryStatusNote) @@ -105,14 +107,18 @@ def test_military_audit_invalid_status_returns_400(self): """Test that an invalid military status returns 400 error.""" from handlers.military_audit import military_audit_handler - event, _ = self._when_testing_military_audit('foo', - 'Documentation verified',) + event, _ = self._when_testing_military_audit( + 'foo', + 'Documentation verified', + ) response = military_audit_handler(event, self.mock_context) self.assertEqual(400, response['statusCode']) - self.assertEqual({'message': "Invalid request body: {'militaryStatus': ['Must be one of: " - "approved, declined.']}"}, json.loads(response['body'])) + self.assertEqual( + {'message': "Invalid request body: {'militaryStatus': ['Must be one of: approved, declined.']}"}, + json.loads(response['body']), + ) def test_military_audit_missing_status_returns_400(self): """Test that missing military status returns 400 error.""" @@ -145,9 +151,7 @@ def test_military_audit_no_affiliation_returns_404(self): # Only create provider, no military affiliation test_provider = self.test_data_generator.put_default_provider_record_in_provider_table() - event, _ = self._when_testing_military_audit('approved', - 'Documentation verified', - test_provider=test_provider) + event, _ = self._when_testing_military_audit('approved', 'Documentation verified', test_provider=test_provider) response = military_audit_handler(event, self.mock_context) @@ -190,9 +194,7 @@ def test_military_audit_creates_provider_update_record(self): ) self.test_data_generator.put_default_military_affiliation_in_provider_table() - event, _ = self._when_testing_military_audit('approved', - 'Documentation verified', - test_provider=test_provider) + event, _ = self._when_testing_military_audit('approved', 'Documentation verified', test_provider=test_provider) response = military_audit_handler(event, self.mock_context) self.assertEqual(200, response['statusCode'], msg=json.loads(response['body'])) @@ -219,4 +221,3 @@ def test_military_audit_creates_provider_update_record(self): # Verify updated values self.assertEqual('approved', update_data.updatedValues['militaryStatus']) self.assertEqual('Documentation verified', update_data.updatedValues['militaryStatusNote']) - diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_s3_events.py b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_s3_events.py index 88ec01624..32f01e434 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_s3_events.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_s3_events.py @@ -1,5 +1,5 @@ import json -from datetime import UTC, datetime +from datetime import UTC, datetime, timedelta from boto3.dynamodb.conditions import Key from moto import mock_aws @@ -33,6 +33,16 @@ def _call_post_military_affiliation_endpoint(self): return provider_users_api_handler(event, self.mock_context) def _when_testing_military_affiliation_s3_object_create_event(self): + from cc_common.data_model.schema.common import MilitaryAuditStatus + + self.test_data_generator.put_default_provider_record_in_provider_table( + value_overrides={ + 'compact': TEST_COMPACT, + 'providerId': TEST_PROVIDER_ID, + 'militaryStatus': MilitaryAuditStatus.DECLINED, + 'militaryStatusNote': 'some declined note', + } + ) # make mock call to POST endpoint to create a military affiliation record in an initializing state post_resp = self._call_post_military_affiliation_endpoint() # make sure the post was successful @@ -73,3 +83,150 @@ def test_provider_user_bucket_event_handler_sets_military_affiliation_status_to_ affiliation_record = self._get_military_affiliation_records() self.assertEqual(1, len(affiliation_record)) self.assertEqual('active', affiliation_record[0]['status']) + + def test_provider_user_bucket_event_handler_sets_latest_military_affiliation_to_active_and_older_to_inactive(self): + """Test that when two military affiliation records are uploaded, the latest is set to + active and older to inactive.""" + from cc_common.data_model.schema.military_affiliation.common import MilitaryAffiliationStatus + from handlers.provider_s3_events import process_provider_s3_events + + # Set up provider record + self.test_data_generator.put_default_provider_record_in_provider_table( + value_overrides={ + 'compact': TEST_COMPACT, + 'providerId': TEST_PROVIDER_ID, + } + ) + + # Create two military affiliation records with different upload dates + # Older record (uploaded 1 day earlier) + older_date = datetime.now(tz=UTC) - timedelta(days=1) + self.test_data_generator.put_default_military_affiliation_in_provider_table( + value_overrides={ + 'compact': TEST_COMPACT, + 'providerId': TEST_PROVIDER_ID, + 'dateOfUpload': older_date, + 'status': MilitaryAffiliationStatus.INITIALIZING.value, + 'fileNames': ['older_military_affiliation.pdf'], + 'documentKeys': [ + f'compact/{TEST_COMPACT}/provider/{TEST_PROVIDER_ID}/' + f'document-type/military-affiliations/' + f'{older_date.date().isoformat()}/older_military_affiliation.pdf' + ], + } + ) + + # Newer record (uploaded today) + newer_date = datetime.now(tz=UTC) + self.test_data_generator.put_default_military_affiliation_in_provider_table( + value_overrides={ + 'compact': TEST_COMPACT, + 'providerId': TEST_PROVIDER_ID, + 'dateOfUpload': newer_date, + 'status': MilitaryAffiliationStatus.INITIALIZING.value, + 'fileNames': [MOCK_MILITARY_AFFILIATION_FILE_NAME], + 'documentKeys': [ + f'compact/{TEST_COMPACT}/provider/{TEST_PROVIDER_ID}/' + f'document-type/military-affiliations/' + f'{newer_date.date().isoformat()}/{MOCK_MILITARY_AFFILIATION_FILE_NAME}' + ], + } + ) + + # Verify both records are in initializing state + affiliation_records = self._get_military_affiliation_records() + self.assertEqual(2, len(affiliation_records)) + for record in affiliation_records: + self.assertEqual('initializing', record['status']) + + # Simulate the S3 bucket event for the newer file + with open('../common/tests/resources/put-event.json') as f: + event = json.load(f) + event['Records'][0]['s3']['object']['key'] = ( + f'compact/{TEST_COMPACT}/provider/{TEST_PROVIDER_ID}/' + f'document-type/military-affiliations/' + f'{newer_date.date().isoformat()}/' + f'{MOCK_MILITARY_AFFILIATION_FILE_NAME}' + ) + + # Process the S3 event + process_provider_s3_events(event, self.mock_context) + + # Verify the newer record is active and the older record is inactive + affiliation_records = self._get_military_affiliation_records() + self.assertEqual(2, len(affiliation_records)) + + # Sort records by dateOfUpload to identify which is which + affiliation_records.sort(key=lambda r: r['dateOfUpload']) + + # Older record should be inactive + self.assertEqual('inactive', affiliation_records[0]['status']) + self.assertEqual(older_date.isoformat(), affiliation_records[0]['dateOfUpload']) + + # Newer record should be active + self.assertEqual('active', affiliation_records[1]['status']) + self.assertEqual(newer_date.isoformat(), affiliation_records[1]['dateOfUpload']) + + def test_provider_user_bucket_event_handler_updates_provider_record_with_tentative_status(self): + """Test that processing military affiliation S3 event updates provider record with tentative + status and empty note.""" + from handlers.provider_s3_events import process_provider_s3_events + + event = self._when_testing_military_affiliation_s3_object_create_event() + + # Verify provider record doesn't have tentative status yet + provider_record_before = self.config.data_client.get_provider_top_level_record( + compact=TEST_COMPACT, provider_id=TEST_PROVIDER_ID + ) + # The default might be 'notApplicable' or might not be set, but it shouldn't be 'tentative' yet + self.assertNotEqual('tentative', provider_record_before.militaryStatus) + + # Process the S3 event + process_provider_s3_events(event, self.mock_context) + + # Verify provider record was updated with tentative status and empty note + updated_provider_record = self.config.data_client.get_provider_top_level_record( + compact=TEST_COMPACT, provider_id=TEST_PROVIDER_ID + ) + + self.assertEqual('tentative', updated_provider_record.militaryStatus) + self.assertEqual('', updated_provider_record.militaryStatusNote) + + def test_provider_user_bucket_event_handler_creates_provider_update_record(self): + """Test that processing military affiliation S3 event creates a provider update record with expected values.""" + from cc_common.data_model.schema.common import UpdateCategory + from cc_common.data_model.schema.provider import ProviderUpdateData + from handlers.provider_s3_events import process_provider_s3_events + + event = self._when_testing_military_affiliation_s3_object_create_event() + + # Get the provider record before processing to use for querying update records + test_provider = self.config.data_client.get_provider_top_level_record( + compact=TEST_COMPACT, provider_id=TEST_PROVIDER_ID + ) + + # Process the S3 event + process_provider_s3_events(event, self.mock_context) + + # Query provider update records + stored_provider_update_records = ( + self.test_data_generator.query_provider_update_records_for_given_record_from_database(test_provider) + ) + + # Verify exactly one update record was created + self.assertEqual(1, len(stored_provider_update_records)) + + # Verify the update record contents + update_data = ProviderUpdateData.from_database_record(stored_provider_update_records[0]) + self.assertEqual(UpdateCategory.MILITARY_FILE_UPLOAD, update_data.updateType) + self.assertEqual(TEST_PROVIDER_ID, str(update_data.providerId)) + self.assertEqual(TEST_COMPACT, update_data.compact) + + # Verify previous state was captured (should be DECLINED with note from setup) + self.assertIsNotNone(update_data.previous) + self.assertEqual('declined', update_data.previous.get('militaryStatus')) + self.assertEqual('some declined note', update_data.previous.get('militaryStatusNote')) + + # Verify updated values + self.assertEqual('tentative', update_data.updatedValues['militaryStatus']) + self.assertEqual('', update_data.updatedValues['militaryStatusNote']) From 9b4a57f1ba67cd09e5dac3be4ab8dc64f6d1eea3 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Tue, 23 Dec 2025 12:29:42 -0700 Subject: [PATCH 07/53] clean up doc/comment --- .../common/cc_common/data_model/provider_record_util.py | 2 +- .../python/data-events/handlers/military_audit_events.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py index f696d8366..6bdc07c7c 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py @@ -777,7 +777,7 @@ def get_military_affiliation_records( """ Get all military affiliation records for this provider. :param filter_condition: An optional filter to apply to the military affiliation records - :return: The most recent military affiliation record if present, else None + :return: The list of military affiliation records for this provider. """ return [ diff --git a/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py b/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py index 4601624fb..a9cb9cd1e 100644 --- a/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py +++ b/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py @@ -52,7 +52,9 @@ def military_audit_notification_listener(message: dict, tracker: NotificationTra provider_email = provider_record.compactConnectRegisteredEmailAddress if not provider_email: - message = 'Provider email not found in system' + # this should not be possible, since only registered providers can upload military documentation + # log the error and raise an exception + message = 'Provider registered email not found in system' logger.error(message) raise CCInternalException(message) From 5e9d37508bae3cbc316cf5ba2cc20cd2651a3108 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Tue, 23 Dec 2025 12:32:57 -0700 Subject: [PATCH 08/53] Add audit approval email template --- .../email-notification-service/lambda.ts | 9 +++ .../lib/email/email-notification-service.ts | 30 ++++++++++ .../tests/email-notification-service.test.ts | 59 +++++++++++++++++++ 3 files changed, 98 insertions(+) diff --git a/backend/compact-connect/lambdas/nodejs/email-notification-service/lambda.ts b/backend/compact-connect/lambdas/nodejs/email-notification-service/lambda.ts index c8672c9da..e64cd3d33 100644 --- a/backend/compact-connect/lambdas/nodejs/email-notification-service/lambda.ts +++ b/backend/compact-connect/lambdas/nodejs/email-notification-service/lambda.ts @@ -367,6 +367,15 @@ export class Lambda implements LambdaInterface { event.templateVariables.recoveryToken ); break; + case 'militaryAuditApprovedNotification': + if (!event.specificEmails?.length) { + throw new Error('No recipients found for military audit approved notification email'); + } + await this.emailService.sendMilitaryAuditApprovedNotificationEmail( + event.compact, + event.specificEmails + ); + break; case 'licenseInvestigationStateNotification': if (!event.jurisdiction) { throw new Error('No jurisdiction provided for license investigation state notification email'); diff --git a/backend/compact-connect/lambdas/nodejs/lib/email/email-notification-service.ts b/backend/compact-connect/lambdas/nodejs/lib/email/email-notification-service.ts index 625c2e21f..40efc9843 100644 --- a/backend/compact-connect/lambdas/nodejs/lib/email/email-notification-service.ts +++ b/backend/compact-connect/lambdas/nodejs/lib/email/email-notification-service.ts @@ -492,4 +492,34 @@ export class EmailNotificationService extends BaseEmailService { await this.sendEmail({ htmlContent, subject, recipients, errorMessage: 'Unable to send provider account recovery confirmation email' }); } + + /** + * Sends a notification email to a provider when their military documentation is approved + * @param compact - The compact name + * @param specificEmails - The email address(es) to send the notification to (provider's email) + */ + public async sendMilitaryAuditApprovedNotificationEmail( + compact: string, + specificEmails: string[] | undefined + ): Promise { + this.logger.info('Sending military audit approved notification email', { compact: compact }); + + const recipients = specificEmails || []; + + if (recipients.length === 0) { + throw new Error('No recipients found for military audit approved notification email'); + } + + const report = this.getNewEmailTemplate(); + const subject = 'Military Documentation Approved - Compact Connect'; + const bodyText = 'This message is to notify you that your military documentation has been reviewed and approved by the compact administration.'; + + 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 military audit approved notification email' }); + } } diff --git a/backend/compact-connect/lambdas/nodejs/tests/email-notification-service.test.ts b/backend/compact-connect/lambdas/nodejs/tests/email-notification-service.test.ts index 83d3e7ab9..4dc881168 100644 --- a/backend/compact-connect/lambdas/nodejs/tests/email-notification-service.test.ts +++ b/backend/compact-connect/lambdas/nodejs/tests/email-notification-service.test.ts @@ -1503,6 +1503,65 @@ describe('EmailNotificationServiceLambda', () => { }); }); + describe('Military Audit Approved Notification', () => { + const SAMPLE_MILITARY_AUDIT_APPROVED_NOTIFICATION_EVENT: EmailNotificationEvent = { + template: 'militaryAuditApprovedNotification', + recipientType: 'SPECIFIC', + compact: 'aslp', + specificEmails: ['provider@example.com'], + templateVariables: {} + }; + + it('should successfully send military audit approved notification email', async () => { + const response = await lambda.handler(SAMPLE_MILITARY_AUDIT_APPROVED_NOTIFICATION_EVENT, {} as any); + + expect(response).toEqual({ + message: 'Email message sent' + }); + + // Verify email was sent with correct parameters + expect(mockSESClient).toHaveReceivedCommandWith(SendEmailCommand, { + Destination: { + ToAddresses: ['provider@example.com'] + }, + Content: { + Simple: { + Body: { + Html: { + Charset: 'UTF-8', + Data: expect.any(String) + } + }, + Subject: { + Charset: 'UTF-8', + Data: 'Military Documentation Approved - Compact Connect' + } + } + }, + FromEmailAddress: 'Compact Connect ' + }); + + // 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('This message is to notify you that your military documentation has been reviewed and approved by the compact administration.'); + expect(htmlContent).toContain('Military Documentation Approved - Compact Connect'); + }); + + it('should throw error when no recipients found', async () => { + const eventWithNoRecipients: EmailNotificationEvent = { + ...SAMPLE_MILITARY_AUDIT_APPROVED_NOTIFICATION_EVENT, + specificEmails: [] + }; + + await expect(lambda.handler(eventWithNoRecipients, {} as any)) + .rejects + .toThrow('No recipients found for military audit approved notification email'); + }); + }); + describe('License Investigation State Notification', () => { const SAMPLE_LICENSE_INVESTIGATION_STATE_NOTIFICATION_EVENT: EmailNotificationEvent = { template: 'licenseInvestigationStateNotification', From 8f5e2c7693896c3088a97a0fbc4612cb33036c4e Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Tue, 23 Dec 2025 12:41:01 -0700 Subject: [PATCH 09/53] Add audit declined email template --- .../email-notification-service/lambda.ts | 10 +++ .../lib/email/email-notification-service.ts | 36 +++++++++++ .../tests/email-notification-service.test.ts | 62 +++++++++++++++++++ 3 files changed, 108 insertions(+) diff --git a/backend/compact-connect/lambdas/nodejs/email-notification-service/lambda.ts b/backend/compact-connect/lambdas/nodejs/email-notification-service/lambda.ts index e64cd3d33..0d61c8718 100644 --- a/backend/compact-connect/lambdas/nodejs/email-notification-service/lambda.ts +++ b/backend/compact-connect/lambdas/nodejs/email-notification-service/lambda.ts @@ -376,6 +376,16 @@ export class Lambda implements LambdaInterface { event.specificEmails ); break; + case 'militaryAuditDeclinedNotification': + if (!event.specificEmails?.length) { + throw new Error('No recipients found for military audit declined notification email'); + } + await this.emailService.sendMilitaryAuditDeclinedNotificationEmail( + event.compact, + event.specificEmails, + event.templateVariables?.auditNote || '' + ); + break; case 'licenseInvestigationStateNotification': if (!event.jurisdiction) { throw new Error('No jurisdiction provided for license investigation state notification email'); diff --git a/backend/compact-connect/lambdas/nodejs/lib/email/email-notification-service.ts b/backend/compact-connect/lambdas/nodejs/lib/email/email-notification-service.ts index 40efc9843..e7f75dd5b 100644 --- a/backend/compact-connect/lambdas/nodejs/lib/email/email-notification-service.ts +++ b/backend/compact-connect/lambdas/nodejs/lib/email/email-notification-service.ts @@ -522,4 +522,40 @@ export class EmailNotificationService extends BaseEmailService { await this.sendEmail({ htmlContent, subject, recipients, errorMessage: 'Unable to send military audit approved notification email' }); } + + /** + * Sends a notification email to a provider when their military documentation is declined + * @param compact - The compact name + * @param specificEmails - The email address(es) to send the notification to (provider's email) + * @param auditNote - Note from the admin explaining the decline + */ + public async sendMilitaryAuditDeclinedNotificationEmail( + compact: string, + specificEmails: string[] | undefined, + auditNote: string + ): Promise { + this.logger.info('Sending military audit declined notification email', { compact: compact }); + + const recipients = specificEmails || []; + + if (recipients.length === 0) { + throw new Error('No recipients found for military audit declined notification email'); + } + + const report = this.getNewEmailTemplate(); + const subject = 'Military Documentation Declined - Compact Connect'; + let bodyText = 'This message is to notify you that your military documentation has been reviewed and declined by the compact administration.'; + + if (auditNote && auditNote.trim().length > 0) { + bodyText += `\n\nMessage from the compact administration: ${auditNote}`; + } + + 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 military audit declined notification email' }); + } } diff --git a/backend/compact-connect/lambdas/nodejs/tests/email-notification-service.test.ts b/backend/compact-connect/lambdas/nodejs/tests/email-notification-service.test.ts index 4dc881168..4b77f5719 100644 --- a/backend/compact-connect/lambdas/nodejs/tests/email-notification-service.test.ts +++ b/backend/compact-connect/lambdas/nodejs/tests/email-notification-service.test.ts @@ -1562,6 +1562,68 @@ describe('EmailNotificationServiceLambda', () => { }); }); + describe('Military Audit Declined Notification', () => { + const SAMPLE_MILITARY_AUDIT_DECLINED_NOTIFICATION_EVENT: EmailNotificationEvent = { + template: 'militaryAuditDeclinedNotification', + recipientType: 'SPECIFIC', + compact: 'aslp', + specificEmails: ['provider@example.com'], + templateVariables: { + auditNote: 'The documentation provided was incomplete and did not meet the required standards.' + } + }; + + it('should successfully send military audit declined notification email', async () => { + const response = await lambda.handler(SAMPLE_MILITARY_AUDIT_DECLINED_NOTIFICATION_EVENT, {} as any); + + expect(response).toEqual({ + message: 'Email message sent' + }); + + // Verify email was sent with correct parameters + expect(mockSESClient).toHaveReceivedCommandWith(SendEmailCommand, { + Destination: { + ToAddresses: ['provider@example.com'] + }, + Content: { + Simple: { + Body: { + Html: { + Charset: 'UTF-8', + Data: expect.any(String) + } + }, + Subject: { + Charset: 'UTF-8', + Data: 'Military Documentation Declined - Compact Connect' + } + } + }, + FromEmailAddress: 'Compact Connect ' + }); + + // 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('This message is to notify you that your military documentation has been reviewed and declined by the compact administration.'); + expect(htmlContent).toContain('Military Documentation Declined - Compact Connect'); + expect(htmlContent).toContain('The documentation provided was incomplete and did not meet the required standards.'); + }); + + it('should throw error when no recipients found', async () => { + const eventWithNoRecipients: EmailNotificationEvent = { + ...SAMPLE_MILITARY_AUDIT_DECLINED_NOTIFICATION_EVENT, + specificEmails: [] + }; + + await expect(lambda.handler(eventWithNoRecipients, {} as any)) + .rejects + .toThrow('No recipients found for military audit declined notification email'); + }); + }); + describe('License Investigation State Notification', () => { const SAMPLE_LICENSE_INVESTIGATION_STATE_NOTIFICATION_EVENT: EmailNotificationEvent = { template: 'licenseInvestigationStateNotification', From 2de09ae4cd500fd3129475b9006acb81151c4999 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Tue, 23 Dec 2025 15:33:59 -0700 Subject: [PATCH 10/53] Remove military status fields when user ends military affiliation --- .../cc_common/data_model/data_client.py | 113 ++++++++++++++++++ .../cc_common/data_model/schema/common.py | 1 + .../handlers/provider_users.py | 2 +- .../test_handlers/test_provider_users.py | 86 +++++++++++-- 4 files changed, 189 insertions(+), 13 deletions(-) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py index dbec0cabe..aa0de2a32 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py @@ -819,6 +819,119 @@ def create_military_affiliation( return latest_military_affiliation_record + @logger_inject_kwargs(logger, 'compact', 'provider_id') + def end_military_affiliation(self, compact: str, provider_id: str) -> None: + """ + End a provider's military affiliation by removing military status fields and deactivating all active records. + + This method: + 1. Removes 'militaryStatus' and 'militaryStatusNote' from the provider record + 2. Creates a provider update record tracking the removal of these fields + 3. Sets all INITIALIZING or ACTIVE military affiliation records to INACTIVE + + All operations are performed in a DynamoDB transaction to ensure consistency. + + :param compact: The compact name + :param provider_id: The provider id + :raises CCNotFoundException: If provider not found + """ + logger.info('Ending military affiliation for provider') + + # Get provider records + provider_user_records = self.get_provider_user_records(compact=compact, provider_id=provider_id) + provider_record = provider_user_records.get_provider_record() + + # Capture previous state before updating + previous_provider_state = provider_record.to_dict() + + # Get all military affiliation records that are INITIALIZING or ACTIVE + active_military_affiliation_records = provider_user_records.get_military_affiliation_records( + filter_condition=lambda record: record.status + in [MilitaryAffiliationStatus.INITIALIZING.value, MilitaryAffiliationStatus.ACTIVE.value] + ) + + # Create provider update record to track the removal of military status fields + now = config.current_standard_datetime + removed_values = [] + if previous_provider_state.get('militaryStatus') is not None: + removed_values.append('militaryStatus') + if previous_provider_state.get('militaryStatusNote') is not None: + removed_values.append('militaryStatusNote') + + update_record_data = { + 'type': ProviderRecordType.PROVIDER_UPDATE, + 'updateType': UpdateCategory.MILITARY_AFFILIATION_ENDED, + 'providerId': provider_id, + 'compact': compact, + 'previous': previous_provider_state, + 'createDate': now, + 'updatedValues': {}, + } + if removed_values: + update_record_data['removedValues'] = removed_values + + provider_update_record = ProviderUpdateData.create_new(update_record_data) + + # Build transaction items + transaction_items = [] + + # Update provider record to remove militaryStatus and militaryStatusNote + provider_serialized_record = provider_record.serialize_to_database_record() + transaction_items.append( + { + 'Update': { + 'TableName': self.config.provider_table_name, + 'Key': { + 'pk': {'S': provider_serialized_record['pk']}, + 'sk': {'S': provider_serialized_record['sk']}, + }, + 'UpdateExpression': ('SET dateOfUpdate = :dateOfUpdate REMOVE militaryStatus, militaryStatusNote'), + 'ExpressionAttributeValues': { + ':dateOfUpdate': {'S': self.config.current_standard_datetime.isoformat()}, + }, + 'ConditionExpression': 'attribute_exists(pk)', + } + } + ) + + # Create provider update record + transaction_items.append( + { + 'Put': { + 'TableName': self.config.provider_table_name, + 'Item': TypeSerializer().serialize(provider_update_record.serialize_to_database_record())['M'], + } + } + ) + + # Update all active/initializing military affiliation records to inactive + for record in active_military_affiliation_records: + record.update({'status': MilitaryAffiliationStatus.INACTIVE.value}) + serialized_record = record.serialize_to_database_record() + transaction_items.append( + { + 'Put': { + 'TableName': self.config.provider_table_name, + 'Item': TypeSerializer().serialize(serialized_record)['M'], + } + } + ) + + # Execute transaction in batches if needed (DynamoDB limit is 100 items) + batch_size = 100 + while transaction_items: + batch = transaction_items[:batch_size] + transaction_items = transaction_items[batch_size:] + + try: + self.config.dynamodb_client.transact_write_items(TransactItems=batch) + logger.info('Successfully processed military affiliation end batch', batch_size=len(batch)) + except ClientError as e: + logger.error('Failed to process military affiliation end transaction', error=str(e)) + raise CCAwsServiceException('Failed to end military affiliation') from e + + logger.info('Successfully ended military affiliation for provider') + def inactivate_military_affiliation_status(self, compact: str, provider_id: str): """ Sets all military affiliation records to an inactive status for a provider in the database. diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py index 6b92c8216..d201a9676 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py @@ -309,6 +309,7 @@ class UpdateCategory(CCEnum): RENEWAL = 'renewal' MILITARY_AUDIT = 'militaryAudit' MILITARY_FILE_UPLOAD = 'militaryFileUpload' + MILITARY_AFFILIATION_ENDED = 'militaryAffiliationEnded' ENCUMBRANCE = 'encumbrance' INVESTIGATION = 'investigation' CLOSING_INVESTIGATION = 'closingInvestigation' diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/provider_users.py b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/provider_users.py index 98f671bc4..78f3962cf 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/provider_users.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/provider_users.py @@ -196,7 +196,7 @@ def _patch_provider_military_affiliation(event, context): # noqa: ARG001 unused if event_body.get('status') != 'inactive': raise CCInvalidRequestException('Invalid status value. Only "inactive" is allowed.') - config.data_client.inactivate_military_affiliation_status(compact=compact, provider_id=provider_id) + config.data_client.end_military_affiliation(compact=compact, provider_id=provider_id) return {'message': 'Military affiliation updated successfully'} diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users.py b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users.py index d36f66edf..f96ec5414 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users.py @@ -6,9 +6,7 @@ from cc_common.config import config from cc_common.exceptions import CCInternalException from common_test.test_constants import ( - DEFAULT_COMPACT, DEFAULT_DATE_OF_UPDATE_TIMESTAMP, - DEFAULT_PROVIDER_ID, DEFAULT_PROVIDER_UPDATE_DATETIME, ) from moto import mock_aws @@ -338,9 +336,7 @@ def test_post_provider_returns_200_if_file_extensions_valid(self): @mock_aws @patch('cc_common.config._Config.current_standard_datetime', datetime.fromisoformat('2024-11-08T23:59:59+00:00')) class TestPatchProviderMilitaryAffiliation(TstFunction): - def _when_testing_patch_provider_user_military_affiliation_event_with_custom_claims(self): - self._load_provider_data() - test_provider = self.test_data_generator.put_default_provider_record_in_provider_table() + def _generate_path_api_event(self, test_provider): with open('../common/tests/resources/api-event.json') as f: event = json.load(f) event['httpMethod'] = 'PATCH' @@ -351,6 +347,11 @@ def _when_testing_patch_provider_user_military_affiliation_event_with_custom_cla return event + def _when_testing_patch_provider_user_military_affiliation_event_with_custom_claims(self): + self._load_provider_data() + test_provider = self.test_data_generator.put_default_provider_record_in_provider_table() + return self._generate_path_api_event(test_provider) + def _get_military_affiliation_records(self, event): provider_id = event['requestContext']['authorizer']['claims']['custom:providerId'] return self.config.provider_table.query( @@ -410,14 +411,9 @@ def test_patch_provider_military_affiliation_updates_status_when_initializing(se from handlers.provider_users import provider_users_api_handler self.test_data_generator.put_default_military_affiliation_in_provider_table({'status': 'initializing'}) + test_provider = self.test_data_generator.put_default_provider_record_in_provider_table() - with open('../common/tests/resources/api-event.json') as f: - event = json.load(f) - event['httpMethod'] = 'PATCH' - event['resource'] = '/v1/provider-users/me/military-affiliation' - event['requestContext']['authorizer']['claims']['custom:providerId'] = DEFAULT_PROVIDER_ID - event['requestContext']['authorizer']['claims']['custom:compact'] = DEFAULT_COMPACT - event['body'] = json.dumps({'status': 'inactive'}) + event = self._generate_path_api_event(test_provider=test_provider) # get the military affiliation record loaded in the test setup and confirm it is initializing affiliation_record = self._get_military_affiliation_records(event) @@ -433,3 +429,69 @@ def test_patch_provider_military_affiliation_updates_status_when_initializing(se self.assertEqual(1, len(affiliation_record)) self.assertEqual('inactive', affiliation_record[0]['status']) + + def test_patch_provider_military_affiliation_removes_military_status_fields(self): + """Test that ending military affiliation removes militaryStatus and militaryStatusNote from provider record.""" + from handlers.provider_users import provider_users_api_handler + + # Create provider with declined military status and a note + test_provider = self.test_data_generator.put_default_provider_record_in_provider_table( + value_overrides={'militaryStatus': 'declined', 'militaryStatusNote': 'test note'} + ) + self.test_data_generator.put_default_military_affiliation_in_provider_table() + + # Create and execute PATCH event + event = self._generate_path_api_event(test_provider) + + resp = provider_users_api_handler(event, self.mock_context) + self.assertEqual(200, resp['statusCode']) + + # Verify fields are removed from provider record + updated_provider_record = self.config.data_client.get_provider_top_level_record( + compact=test_provider.compact, provider_id=test_provider.providerId + ) + + self.assertIsNone(updated_provider_record.militaryStatus) + self.assertIsNone(updated_provider_record.militaryStatusNote) + + def test_patch_provider_military_affiliation_creates_provider_update_record(self): + """Test that ending military affiliation creates a provider update record with expected values.""" + from cc_common.data_model.schema.common import UpdateCategory + from cc_common.data_model.schema.provider import ProviderUpdateData + from handlers.provider_users import provider_users_api_handler + + self._load_provider_data() + + # Create provider with declined military status and a note + test_provider = self.test_data_generator.put_default_provider_record_in_provider_table( + value_overrides={'militaryStatus': 'declined', 'militaryStatusNote': 'test note'} + ) + self.test_data_generator.put_default_military_affiliation_in_provider_table() + + # Create and execute PATCH event + event = self._generate_path_api_event(test_provider) + + resp = provider_users_api_handler(event, self.mock_context) + self.assertEqual(200, resp['statusCode']) + + # Query provider update records + stored_provider_update_records = ( + self.test_data_generator.query_provider_update_records_for_given_record_from_database(test_provider) + ) + + # Verify exactly one update record was created + self.assertEqual(1, len(stored_provider_update_records)) + + # Verify the update record contents + update_data = ProviderUpdateData.from_database_record(stored_provider_update_records[0]) + self.assertEqual(UpdateCategory.MILITARY_AFFILIATION_ENDED, update_data.updateType) + + # Verify previous state was captured + self.assertIsNotNone(update_data.previous) + self.assertEqual('declined', update_data.previous.get('militaryStatus')) + self.assertEqual('test note', update_data.previous.get('militaryStatusNote')) + + # Verify removed values + self.assertEqual(['militaryStatus', 'militaryStatusNote'], update_data.removedValues) + # Verify updated values is empty (no fields were updated, only removed) + self.assertEqual({}, update_data.updatedValues) From 4da19fd7ca8dbeee31e8ff191bf165524f6b73b8 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Tue, 23 Dec 2025 15:47:30 -0700 Subject: [PATCH 11/53] Use update statement to update provider status values --- .../cc_common/data_model/data_client.py | 29 ++++++++++++------- .../smoke/military_affiliation_smoke_tests.py | 25 ++++++++++++++++ 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py index aa0de2a32..6904502ff 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py @@ -703,7 +703,7 @@ def complete_military_affiliation_initialization(self, compact: str, provider_id { 'type': ProviderRecordType.PROVIDER_UPDATE, 'updateType': UpdateCategory.MILITARY_FILE_UPLOAD, - 'providerId': UUID(provider_id), + 'providerId': provider_id, 'compact': compact, 'previous': provider_record.to_dict(), 'createDate': now, @@ -714,20 +714,27 @@ def complete_military_affiliation_initialization(self, compact: str, provider_id } ) - # Update provider record with militaryStatus set to TENTATIVE - provider_record.update( - { - 'militaryStatus': MilitaryAuditStatus.TENTATIVE.value, - 'militaryStatusNote': '', - } - ) - # Build transaction items with provider and provider update record + provider_serialized_record = provider_record.serialize_to_database_record() transaction_items = [ { - 'Put': { + 'Update': { 'TableName': self.config.provider_table_name, - 'Item': TypeSerializer().serialize(provider_record.serialize_to_database_record())['M'], + 'Key': { + 'pk': {'S': provider_serialized_record['pk']}, + 'sk': {'S': provider_serialized_record['sk']}, + }, + 'UpdateExpression': ( + 'SET militaryStatus = :militaryStatus, ' + 'militaryStatusNote = :militaryStatusNote, ' + 'dateOfUpdate = :dateOfUpdate' + ), + 'ExpressionAttributeValues': { + ':militaryStatus': {'S': MilitaryAuditStatus.TENTATIVE.value}, + ':militaryStatusNote': {'S': ''}, + ':dateOfUpdate': {'S': self.config.current_standard_datetime.isoformat()}, + }, + 'ConditionExpression': 'attribute_exists(pk)', } }, { diff --git a/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py b/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py index 1e141ef26..cc1274fc6 100644 --- a/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py +++ b/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py @@ -87,6 +87,19 @@ def test_military_affiliation_upload(): raise SmokeTestFailureException(f'Failed to GET provider data. Response: {get_provider_data_response.json()}') provider_data = get_provider_data_response.json() + + # check the militaryStatus of the provider to make sure it is now reset to 'tenative' + if provider_data.get('militaryStatus') != 'tentative': + raise SmokeTestFailureException( + f'Military status is not tentative. Status: {provider_data.get("militaryStatus")}' + ) + if provider_data.get('militaryStatusNote') != '': + raise SmokeTestFailureException( + f'Military status note is not empty. Note: {provider_data.get("militaryStatusNote")}' + ) + + print(f'Successfully updated military status to tentative. Status: {provider_data.get("militaryStatus")}') + military_affiliations = provider_data.get('militaryAffiliations') if not military_affiliations: raise SmokeTestFailureException('No military affiliations found in provider data') @@ -140,6 +153,18 @@ def test_military_affiliation_patch_update(): print(f'Successfully updated military affiliation records: {military_affiliations}') + # check the militaryStatus of the provider to make sure it is now reset to 'notApplicable' + if provider_data.get('militaryStatus') != 'notApplicable': + raise SmokeTestFailureException( + f'Military status is not notApplicable. Status: {provider_data.get("militaryStatus")}' + ) + if provider_data.get('militaryStatusNote') != '': + raise SmokeTestFailureException( + f'Military status note is not empty. Note: {provider_data.get("militaryStatusNote")}' + ) + + print(f'Successfully updated military status to notApplicable. Status: {provider_data.get("militaryStatus")}') + if __name__ == '__main__': load_smoke_test_env() From 693e94868ed676af49894603392466801cfa7f88 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Tue, 23 Dec 2025 16:00:24 -0700 Subject: [PATCH 12/53] Use update statement during audit processing --- .../cc_common/data_model/data_client.py | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py index 6904502ff..807559eb6 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py @@ -1012,22 +1012,29 @@ def process_military_audit( } ) - # Update provider record with military status - provider_record.update( - { - 'militaryStatus': military_status.value, - 'militaryStatusNote': note_value, - } - ) - # Execute both updates in a transaction + provider_serialized_record = provider_record.serialize_to_database_record() self.config.dynamodb_client.transact_write_items( TransactItems=[ # Update provider record { - 'Put': { + 'Update': { 'TableName': self.config.provider_table_name, - 'Item': TypeSerializer().serialize(provider_record.serialize_to_database_record())['M'], + 'Key': { + 'pk': {'S': provider_serialized_record['pk']}, + 'sk': {'S': provider_serialized_record['sk']}, + }, + 'UpdateExpression': ( + 'SET militaryStatus = :militaryStatus, ' + 'militaryStatusNote = :militaryStatusNote, ' + 'dateOfUpdate = :dateOfUpdate' + ), + 'ExpressionAttributeValues': { + ':militaryStatus': {'S': military_status.value}, + ':militaryStatusNote': {'S': note_value}, + ':dateOfUpdate': {'S': self.config.current_standard_datetime.isoformat()}, + }, + 'ConditionExpression': 'attribute_exists(pk)', } }, # Create provider update record From da1bae6ef971997b1c664fc10cac3370fac2c074 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Tue, 23 Dec 2025 17:04:04 -0700 Subject: [PATCH 13/53] Add smoke test for military audit endpoint --- .../smoke/military_affiliation_smoke_tests.py | 92 ++++++++++++++++++- 1 file changed, 90 insertions(+), 2 deletions(-) diff --git a/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py b/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py index cc1274fc6..e1a223d63 100644 --- a/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py +++ b/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py @@ -7,8 +7,12 @@ import requests from smoke_common import ( SmokeTestFailureException, + call_provider_users_me_endpoint, + create_test_staff_user, + delete_test_staff_user, get_api_base_url, get_provider_user_auth_headers_cached, + get_staff_user_auth_headers, load_smoke_test_env, ) @@ -98,7 +102,7 @@ def test_military_affiliation_upload(): f'Military status note is not empty. Note: {provider_data.get("militaryStatusNote")}' ) - print(f'Successfully updated military status to tentative. Status: {provider_data.get("militaryStatus")}') + print('Successfully updated military status to tentative.') military_affiliations = provider_data.get('militaryAffiliations') if not military_affiliations: @@ -163,10 +167,94 @@ def test_military_affiliation_patch_update(): f'Military status note is not empty. Note: {provider_data.get("militaryStatusNote")}' ) - print(f'Successfully updated military status to notApplicable. Status: {provider_data.get("militaryStatus")}') + print('Successfully updated military status to notApplicable.') + + +def test_military_affiliation_audit(): + """ + Test the military affiliation audit flow where a compact admin audits a provider's military records. + + Step 1: Get the compact and provider id of the provider + Step 2: Create a staff user with compact admin privileges + Step 3: Use that staff user to call the PATCH military audit endpoint with 'declined' status and a note + Step 4: Verify the provider's militaryStatus and militaryStatusNote are updated correctly + """ + # Step 1: Get the compact and provider id of the provider + provider_data = call_provider_users_me_endpoint() + provider_id = provider_data.get('providerId') + compact = provider_data.get('compact') + + if not provider_id or not compact: + raise SmokeTestFailureException('Failed to get provider id or compact from provider data') + + print(f'Testing military affiliation audit for provider {provider_id} in compact {compact}') + + # Step 2: Create a staff user with compact admin privileges + test_staff_user_email = 'testStaffUserMilitaryAudit@smokeTestFakeEmail.com' + test_user_sub = create_test_staff_user( + email=test_staff_user_email, + compact=compact, + jurisdiction='oh', + permissions={'actions': {'admin'}, 'jurisdictions': {'oh': {'write', 'admin'}}}, + ) + + try: + # Get staff user auth headers + staff_headers = get_staff_user_auth_headers(test_staff_user_email) + + # Step 3: Use that staff user to call the PATCH military audit endpoint + test_military_status_note = 'Test audit note: Documentation was unclear and needs to be resubmitted.' + patch_body = { + 'militaryStatus': 'declined', + 'militaryStatusNote': test_military_status_note, + } + + patch_api_response = requests.patch( + url=get_api_base_url() + f'/v1/compacts/{compact}/providers/{provider_id}/militaryAudit', + headers=staff_headers, + json=patch_body, + timeout=10, + ) + + if patch_api_response.status_code != 200: + raise SmokeTestFailureException(f'Failed to PATCH military audit. Response: {patch_api_response.json()}') + print('Successfully called PATCH military audit endpoint.') + + # Step 4: Get the provider's information again and verify the militaryStatus and militaryStatusNote + provider_headers = get_provider_user_auth_headers_cached() + get_provider_data_response = requests.get( + get_api_base_url() + '/v1/provider-users/me', headers=provider_headers, timeout=10 + ) + + if get_provider_data_response.status_code != 200: + raise SmokeTestFailureException( + f'Failed to GET provider data. Response: {get_provider_data_response.json()}' + ) + + updated_provider_data = get_provider_data_response.json() + + # Verify militaryStatus is set to declined + if updated_provider_data.get('militaryStatus') != 'declined': + raise SmokeTestFailureException( + f'Military status is not declined. Status: {updated_provider_data.get("militaryStatus")}' + ) + + # Verify militaryStatusNote equals what was passed into the request body + if updated_provider_data.get('militaryStatusNote') != test_military_status_note: + raise SmokeTestFailureException( + f'Military status note does not match. Expected: "{test_military_status_note}", ' + f'Got: "{updated_provider_data.get("militaryStatusNote")}"' + ) + + print('Successfully verified military status audit.') + + finally: + # Clean up the test staff user + delete_test_staff_user(test_staff_user_email, user_sub=test_user_sub, compact=compact) if __name__ == '__main__': load_smoke_test_env() test_military_affiliation_upload() test_military_affiliation_patch_update() + test_military_affiliation_audit() From 7706d93822c36d7cc3875e91dfa3b2e8bd9f06d4 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Fri, 26 Dec 2025 11:09:37 -0700 Subject: [PATCH 14/53] Add smoke test for approved audit flow --- .../smoke/military_affiliation_smoke_tests.py | 88 ++++++++++++------- 1 file changed, 58 insertions(+), 30 deletions(-) diff --git a/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py b/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py index e1a223d63..142df36bd 100644 --- a/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py +++ b/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py @@ -170,6 +170,34 @@ def test_military_affiliation_patch_update(): print('Successfully updated military status to notApplicable.') +def _call_audit_endpoint_and_get_provider_data(compact, provider_id, test_staff_user_email, patch_body): + # Get staff user auth headers + staff_headers = get_staff_user_auth_headers(test_staff_user_email) + + patch_api_response = requests.patch( + url=get_api_base_url() + f'/v1/compacts/{compact}/providers/{provider_id}/militaryAudit', + headers=staff_headers, + json=patch_body, + timeout=10, + ) + + if patch_api_response.status_code != 200: + raise SmokeTestFailureException(f'Failed to PATCH military audit. Response: {patch_api_response.json()}') + print('Successfully called PATCH military audit endpoint.') + + provider_headers = get_provider_user_auth_headers_cached() + get_provider_data_response = requests.get( + get_api_base_url() + '/v1/provider-users/me', headers=provider_headers, timeout=10 + ) + + if get_provider_data_response.status_code != 200: + raise SmokeTestFailureException( + f'Failed to GET provider data. Response: {get_provider_data_response.json()}' + ) + + return get_provider_data_response.json() + + def test_military_affiliation_audit(): """ Test the military affiliation audit flow where a compact admin audits a provider's military records. @@ -199,39 +227,16 @@ def test_military_affiliation_audit(): ) try: - # Get staff user auth headers - staff_headers = get_staff_user_auth_headers(test_staff_user_email) - - # Step 3: Use that staff user to call the PATCH military audit endpoint - test_military_status_note = 'Test audit note: Documentation was unclear and needs to be resubmitted.' + # Step 3: Use that staff user to call the PATCH military audit endpoint with a decline message + test_military_status_note = 'Documentation was unclear and needs to be resubmitted.' patch_body = { 'militaryStatus': 'declined', 'militaryStatusNote': test_military_status_note, } - - patch_api_response = requests.patch( - url=get_api_base_url() + f'/v1/compacts/{compact}/providers/{provider_id}/militaryAudit', - headers=staff_headers, - json=patch_body, - timeout=10, - ) - - if patch_api_response.status_code != 200: - raise SmokeTestFailureException(f'Failed to PATCH military audit. Response: {patch_api_response.json()}') - print('Successfully called PATCH military audit endpoint.') - - # Step 4: Get the provider's information again and verify the militaryStatus and militaryStatusNote - provider_headers = get_provider_user_auth_headers_cached() - get_provider_data_response = requests.get( - get_api_base_url() + '/v1/provider-users/me', headers=provider_headers, timeout=10 - ) - - if get_provider_data_response.status_code != 200: - raise SmokeTestFailureException( - f'Failed to GET provider data. Response: {get_provider_data_response.json()}' - ) - - updated_provider_data = get_provider_data_response.json() + updated_provider_data = _call_audit_endpoint_and_get_provider_data(compact, + provider_id, + test_staff_user_email, + patch_body) # Verify militaryStatus is set to declined if updated_provider_data.get('militaryStatus') != 'declined': @@ -246,7 +251,30 @@ def test_military_affiliation_audit(): f'Got: "{updated_provider_data.get("militaryStatusNote")}"' ) - print('Successfully verified military status audit.') + print('Successfully verified declined military status audit.') + + # Step 4: Use that staff user to call the PATCH military audit endpoint with an approved message + patch_body = { + 'militaryStatus': 'approved' + } + provider_data_after_approval = _call_audit_endpoint_and_get_provider_data(compact, + provider_id, + test_staff_user_email, + patch_body) + # Verify militaryStatus is set to declined + if provider_data_after_approval.get('militaryStatus') != 'approved': + raise SmokeTestFailureException( + f'Military status is not declined. Status: {provider_data_after_approval.get("militaryStatus")}' + ) + + # Verify militaryStatusNote equals what was passed into the request body + if provider_data_after_approval.get('militaryStatusNote') != '': + raise SmokeTestFailureException( + f'Military status note does not match. Expected empty string, ' + f'Got: "{provider_data_after_approval.get("militaryStatusNote")}"' + ) + + print('Successfully verified approved military status audit.') finally: # Clean up the test staff user From ac5745c80aa81f9c2cb6aade659537c3ab729b8a Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Fri, 26 Dec 2025 11:18:32 -0700 Subject: [PATCH 15/53] Fix comments --- .../python/data-events/handlers/military_audit_events.py | 2 +- .../tests/smoke/military_affiliation_smoke_tests.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py b/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py index a9cb9cd1e..c63bec32d 100644 --- a/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py +++ b/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py @@ -8,7 +8,7 @@ @sqs_handler_with_notification_tracking -def military_audit_notification_listener(message: dict, tracker: NotificationTracker): # noqa: ARG001 +def military_audit_notification_listener(message: dict, tracker: NotificationTracker): """ Handle military audit events and send notifications to providers. diff --git a/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py b/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py index 142df36bd..02bdcbce5 100644 --- a/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py +++ b/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py @@ -92,7 +92,7 @@ def test_military_affiliation_upload(): provider_data = get_provider_data_response.json() - # check the militaryStatus of the provider to make sure it is now reset to 'tenative' + # check the militaryStatus of the provider to make sure it is now reset to 'tentative' if provider_data.get('militaryStatus') != 'tentative': raise SmokeTestFailureException( f'Military status is not tentative. Status: {provider_data.get("militaryStatus")}' @@ -264,7 +264,7 @@ def test_military_affiliation_audit(): # Verify militaryStatus is set to declined if provider_data_after_approval.get('militaryStatus') != 'approved': raise SmokeTestFailureException( - f'Military status is not declined. Status: {provider_data_after_approval.get("militaryStatus")}' + f'Military status is not approved. Status: {provider_data_after_approval.get("militaryStatus")}' ) # Verify militaryStatusNote equals what was passed into the request body From 5d2bfee628198b1f98506265600087fd19f6df3f Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Fri, 26 Dec 2025 12:46:33 -0700 Subject: [PATCH 16/53] Using full datetime for military affiliation records This enhances an earlier design decision regarding how the military files are stored in S3 and how the metadata is stored in DynamoDB. By using the full datetime we ensure that every DynamoDB records maps to a specific file upload in S3. --- .../data_model/schema/military_affiliation/record.py | 4 ++-- .../common/tests/resources/dynamo/military-affiliation.json | 2 +- .../python/provider-data-v1/handlers/provider_users.py | 2 +- .../tests/function/test_handlers/test_provider_users.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/record.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/record.py index 15457b945..abb84e614 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/record.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/record.py @@ -36,6 +36,6 @@ class MilitaryAffiliationRecordSchema(BaseRecordSchema): @pre_dump def generate_pk_sk(self, in_data, **kwargs): # noqa: ARG001 unused-argument in_data['pk'] = f'{in_data["compact"]}#PROVIDER#{in_data["providerId"]}' - upload_date = in_data['dateOfUpload'].date().isoformat() - in_data['sk'] = f'{in_data["compact"]}#PROVIDER#military-affiliation#{upload_date}' + upload_datetime = in_data['dateOfUpload'].isoformat() + in_data['sk'] = f'{in_data["compact"]}#PROVIDER#military-affiliation#{upload_datetime}' return in_data diff --git a/backend/compact-connect/lambdas/python/common/tests/resources/dynamo/military-affiliation.json b/backend/compact-connect/lambdas/python/common/tests/resources/dynamo/military-affiliation.json index f2aa2314b..6e5d249a8 100644 --- a/backend/compact-connect/lambdas/python/common/tests/resources/dynamo/military-affiliation.json +++ b/backend/compact-connect/lambdas/python/common/tests/resources/dynamo/military-affiliation.json @@ -1,6 +1,6 @@ { "pk": "aslp#PROVIDER#89a6377e-c3a5-40e5-bca5-317ec854c570", - "sk": "aslp#PROVIDER#military-affiliation#2024-11-08", + "sk": "aslp#PROVIDER#military-affiliation#2024-11-08T23:59:59+00:00", "providerId": "89a6377e-c3a5-40e5-bca5-317ec854c570", "compact": "aslp", "type": "militaryAffiliation", diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/provider_users.py b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/provider_users.py index 78f3962cf..e62f67413 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/provider_users.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/provider_users.py @@ -133,7 +133,7 @@ def _post_provider_military_affiliation(event, context): # noqa: ARG001 unused- s3_document_prefix = ( f'compact/{compact}/provider/{provider_id}/document-type/' - f'{MILITARY_AFFILIATIONS_DOCUMENT_TYPE_KEY_NAME}/{config.current_standard_datetime.date().isoformat()}/' + f'{MILITARY_AFFILIATIONS_DOCUMENT_TYPE_KEY_NAME}/{config.current_standard_datetime.isoformat()}/' ) event_body = json.loads(event['body']) diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users.py b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users.py index f96ec5414..978d28c37 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users.py @@ -236,7 +236,7 @@ def test_post_provider_military_affiliation_returns_affiliation_information(self { 'fields': { 'key': f'compact/{TEST_COMPACT}/provider/{provider_id}/document-type/military-affiliations' - f'/2024-11-08/1234#military_affiliation.pdf', + f'/2024-11-08T23:59:59+00:00/1234#military_affiliation.pdf', 'x-amz-algorithm': 'AWS4-HMAC-SHA256', }, 'url': 'https://provider-user-bucket.s3.amazonaws.com/', From f67c03e56fc266c6e86625a0d26fdc5f8b79f149 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Fri, 26 Dec 2025 14:31:31 -0700 Subject: [PATCH 17/53] Refactor tests in common layer --- .../common/tests/function/test_data_client.py | 27 ++++++++++--------- .../api/provider-detail-response.json | 2 +- .../tests/unit/test_sanitize_provider_data.py | 4 ++- .../smoke/military_affiliation_smoke_tests.py | 22 ++++++--------- 4 files changed, 27 insertions(+), 28 deletions(-) diff --git a/backend/compact-connect/lambdas/python/common/tests/function/test_data_client.py b/backend/compact-connect/lambdas/python/common/tests/function/test_data_client.py index 1239b030c..b17bd1aa1 100644 --- a/backend/compact-connect/lambdas/python/common/tests/function/test_data_client.py +++ b/backend/compact-connect/lambdas/python/common/tests/function/test_data_client.py @@ -96,24 +96,27 @@ def _get_military_affiliation_records(self, provider_id: str) -> list[dict]: def test_complete_military_affiliation_initialization_sets_expected_status(self): from cc_common.data_model.data_client import DataClient + provider = self.test_data_generator.put_default_provider_record_in_provider_table() + provider_id = str(provider.providerId) + # Here we are testing an edge case where there are two military affiliation records # both in an initializing state. This could happen in the event of a failed file upload. # We want to ensure that the most recent record is set to active and the older record is # set to inactive. - with open('tests/resources/dynamo/military-affiliation.json') as f: - military_affiliation_record = json.load(f) - military_affiliation_record['status'] = 'initializing' - - military_affiliation_record['sk'] = 'aslp#PROVIDER#military-affiliation#2024-07-08' - military_affiliation_record['dateOfUpload'] = '2024-07-08T13:34:59+00:00' - self._provider_table.put_item(Item=military_affiliation_record) + self.test_data_generator.put_default_military_affiliation_in_provider_table( + { + 'status': 'initializing', + 'dateOfUpload': datetime.fromisoformat('2024-07-08T13:34:59+00:00'), + } + ) # now add record on following day - military_affiliation_record['sk'] = 'aslp#PROVIDER#military-affiliation#2024-07-09' - military_affiliation_record['dateOfUpload'] = '2024-07-09T10:34:59+00:00' - self._provider_table.put_item(Item=military_affiliation_record) - - provider_id = military_affiliation_record['providerId'] + self.test_data_generator.put_default_military_affiliation_in_provider_table( + { + 'status': 'initializing', + 'dateOfUpload': datetime.fromisoformat('2024-07-09T10:34:59+00:00'), + } + ) # assert that two records exist, both in an initializing state military_affiliation_record = self._get_military_affiliation_records(provider_id) diff --git a/backend/compact-connect/lambdas/python/common/tests/resources/api/provider-detail-response.json b/backend/compact-connect/lambdas/python/common/tests/resources/api/provider-detail-response.json index 0e671526a..2bfafa6a1 100644 --- a/backend/compact-connect/lambdas/python/common/tests/resources/api/provider-detail-response.json +++ b/backend/compact-connect/lambdas/python/common/tests/resources/api/provider-detail-response.json @@ -87,7 +87,7 @@ "providerId": "89a6377e-c3a5-40e5-bca5-317ec854c570", "compact": "aslp", "type": "militaryAffiliation", - "documentKeys": ["/provider/89a6377e-c3a5-40e5-bca5-317ec854c570/document-type/military-affiliations/2024-07-08/1234#military-waiver.pdf"], + "documentKeys": ["/provider/89a6377e-c3a5-40e5-bca5-317ec854c570/document-type/military-affiliations/2024-07-08T23:59:59+00:00/1234#military-waiver.pdf"], "affiliationType": "militaryMember", "fileNames": ["military-waiver.pdf"], "status": "active", diff --git a/backend/compact-connect/lambdas/python/common/tests/unit/test_sanitize_provider_data.py b/backend/compact-connect/lambdas/python/common/tests/unit/test_sanitize_provider_data.py index 640154b8b..aa771032e 100644 --- a/backend/compact-connect/lambdas/python/common/tests/unit/test_sanitize_provider_data.py +++ b/backend/compact-connect/lambdas/python/common/tests/unit/test_sanitize_provider_data.py @@ -60,8 +60,10 @@ def when_testing_general_provider_info_returned(self, scopes: set[str]): # now create expected provider record with the ssn and dob removed del expected_provider['ssnLastFour'] del expected_provider['dateOfBirth'] - # we do not return the military affiliation document keys if the caller does not have read private scope + # we do not return the military affiliation document keys or the military status note + # if the caller does not have read private scope del expected_provider['militaryAffiliations'][0]['documentKeys'] + del expected_provider['militaryStatusNote'] # also remove the ssn from the license record del expected_provider['licenses'][0]['ssnLastFour'] del expected_provider['licenses'][0]['dateOfBirth'] diff --git a/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py b/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py index 02bdcbce5..18ba73867 100644 --- a/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py +++ b/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py @@ -191,9 +191,7 @@ def _call_audit_endpoint_and_get_provider_data(compact, provider_id, test_staff_ ) if get_provider_data_response.status_code != 200: - raise SmokeTestFailureException( - f'Failed to GET provider data. Response: {get_provider_data_response.json()}' - ) + raise SmokeTestFailureException(f'Failed to GET provider data. Response: {get_provider_data_response.json()}') return get_provider_data_response.json() @@ -233,10 +231,9 @@ def test_military_affiliation_audit(): 'militaryStatus': 'declined', 'militaryStatusNote': test_military_status_note, } - updated_provider_data = _call_audit_endpoint_and_get_provider_data(compact, - provider_id, - test_staff_user_email, - patch_body) + updated_provider_data = _call_audit_endpoint_and_get_provider_data( + compact, provider_id, test_staff_user_email, patch_body + ) # Verify militaryStatus is set to declined if updated_provider_data.get('militaryStatus') != 'declined': @@ -254,13 +251,10 @@ def test_military_affiliation_audit(): print('Successfully verified declined military status audit.') # Step 4: Use that staff user to call the PATCH military audit endpoint with an approved message - patch_body = { - 'militaryStatus': 'approved' - } - provider_data_after_approval = _call_audit_endpoint_and_get_provider_data(compact, - provider_id, - test_staff_user_email, - patch_body) + patch_body = {'militaryStatus': 'approved'} + provider_data_after_approval = _call_audit_endpoint_and_get_provider_data( + compact, provider_id, test_staff_user_email, patch_body + ) # Verify militaryStatus is set to declined if provider_data_after_approval.get('militaryStatus') != 'approved': raise SmokeTestFailureException( From 4b983f1b63ad56298c982f580cd78eae11d29882 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Fri, 26 Dec 2025 14:39:45 -0700 Subject: [PATCH 18/53] PR feedback - use constant --- .../python/data-events/handlers/military_audit_events.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py b/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py index c63bec32d..d51ad5cf2 100644 --- a/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py +++ b/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py @@ -1,6 +1,7 @@ from uuid import UUID from cc_common.config import config, logger +from cc_common.data_model.schema.common import MilitaryAuditStatus from cc_common.data_model.schema.data_event.api import MilitaryAuditEventDetailSchema from cc_common.event_state_client import EventType, NotificationTracker, RecipientType from cc_common.exceptions import CCInternalException @@ -65,11 +66,11 @@ def military_audit_notification_listener(message: dict, tracker: NotificationTra # Determine event type and send appropriate notification event_type = ( - EventType.MILITARY_AUDIT_APPROVED if audit_result == 'approved' else EventType.MILITARY_AUDIT_DECLINED + EventType.MILITARY_AUDIT_APPROVED if audit_result == MilitaryAuditStatus.APPROVED else EventType.MILITARY_AUDIT_DECLINED ) try: - if audit_result == 'approved': + if audit_result == MilitaryAuditStatus.APPROVED: logger.info('Sending military audit approved notification to provider') config.email_service_client.send_military_audit_approved_notification( compact=compact, From c2077ec3c2199b584e7ada8ee3f82599fad1ab11 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Fri, 26 Dec 2025 14:43:52 -0700 Subject: [PATCH 19/53] fix tests to use updated format for document keys --- .../function/test_handlers/test_provider_s3_events.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_s3_events.py b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_s3_events.py index 32f01e434..820114062 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_s3_events.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_s3_events.py @@ -54,7 +54,7 @@ def _when_testing_military_affiliation_s3_object_create_event(self): event['Records'][0]['s3']['object']['key'] = ( f'compact/{TEST_COMPACT}/provider/{TEST_PROVIDER_ID}/' f'document-type/military-affiliations/' - f'{datetime.now(tz=UTC).date().isoformat()}/' + f'{datetime.now(tz=UTC).isoformat()}/' f'{MOCK_MILITARY_AFFILIATION_FILE_NAME}' ) @@ -111,7 +111,7 @@ def test_provider_user_bucket_event_handler_sets_latest_military_affiliation_to_ 'documentKeys': [ f'compact/{TEST_COMPACT}/provider/{TEST_PROVIDER_ID}/' f'document-type/military-affiliations/' - f'{older_date.date().isoformat()}/older_military_affiliation.pdf' + f'{older_date.isoformat()}/older_military_affiliation.pdf' ], } ) @@ -128,7 +128,7 @@ def test_provider_user_bucket_event_handler_sets_latest_military_affiliation_to_ 'documentKeys': [ f'compact/{TEST_COMPACT}/provider/{TEST_PROVIDER_ID}/' f'document-type/military-affiliations/' - f'{newer_date.date().isoformat()}/{MOCK_MILITARY_AFFILIATION_FILE_NAME}' + f'{newer_date.isoformat()}/{MOCK_MILITARY_AFFILIATION_FILE_NAME}' ], } ) @@ -145,7 +145,7 @@ def test_provider_user_bucket_event_handler_sets_latest_military_affiliation_to_ event['Records'][0]['s3']['object']['key'] = ( f'compact/{TEST_COMPACT}/provider/{TEST_PROVIDER_ID}/' f'document-type/military-affiliations/' - f'{newer_date.date().isoformat()}/' + f'{newer_date.isoformat()}/' f'{MOCK_MILITARY_AFFILIATION_FILE_NAME}' ) From c665495ab14347738c501e06bb93fd90ddf30621 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Fri, 26 Dec 2025 15:58:35 -0700 Subject: [PATCH 20/53] update test to grab latest military affiliation record --- .../python/common/cc_common/data_model/data_client.py | 4 ++-- .../tests/smoke/military_affiliation_smoke_tests.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py index 807559eb6..e61d87017 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py @@ -819,7 +819,7 @@ def create_military_affiliation( # We need to check for any other military affiliations for this provider # and set them to inactive. Note these could be consolidated into a single batch call if performance # becomes an issue. - self.inactivate_military_affiliation_status(compact, provider_id) + self.inactivate_current_military_affiliation_records(compact, provider_id) with self.config.provider_table.batch_writer() as batch: batch.put_item(Item=latest_military_affiliation_record_serialized) @@ -939,7 +939,7 @@ def end_military_affiliation(self, compact: str, provider_id: str) -> None: logger.info('Successfully ended military affiliation for provider') - def inactivate_military_affiliation_status(self, compact: str, provider_id: str): + def inactivate_current_military_affiliation_records(self, compact: str, provider_id: str): """ Sets all military affiliation records to an inactive status for a provider in the database. diff --git a/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py b/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py index 18ba73867..195d0b317 100644 --- a/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py +++ b/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py @@ -107,10 +107,10 @@ def test_military_affiliation_upload(): military_affiliations = provider_data.get('militaryAffiliations') if not military_affiliations: raise SmokeTestFailureException('No military affiliations found in provider data') - today = datetime.now(tz=UTC).date().isoformat() - matching_military_affiliation = next( - (ma for ma in military_affiliations if datetime.fromisoformat(ma['dateOfUpload']).date().isoformat() == today), - None, + # Find the military affiliation record with the most recent dateOfUpload + matching_military_affiliation = max( + military_affiliations, + key=lambda ma: datetime.fromisoformat(ma['dateOfUpload']) ) if not matching_military_affiliation: raise SmokeTestFailureException( From a66ec2cbfb83d180b096970f6dcbca15cb723328 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Fri, 26 Dec 2025 16:21:56 -0700 Subject: [PATCH 21/53] fix test data to use new document key format --- .../common/tests/resources/dynamo/military-affiliation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/compact-connect/lambdas/python/common/tests/resources/dynamo/military-affiliation.json b/backend/compact-connect/lambdas/python/common/tests/resources/dynamo/military-affiliation.json index 6e5d249a8..f00a98008 100644 --- a/backend/compact-connect/lambdas/python/common/tests/resources/dynamo/military-affiliation.json +++ b/backend/compact-connect/lambdas/python/common/tests/resources/dynamo/military-affiliation.json @@ -4,7 +4,7 @@ "providerId": "89a6377e-c3a5-40e5-bca5-317ec854c570", "compact": "aslp", "type": "militaryAffiliation", - "documentKeys": ["/provider/89a6377e-c3a5-40e5-bca5-317ec854c570/document-type/military-affiliations/2024-07-08/1234#military-waiver.pdf"], + "documentKeys": ["/provider/89a6377e-c3a5-40e5-bca5-317ec854c570/document-type/military-affiliations/2024-07-08T23:59:59+00:00/1234#military-waiver.pdf"], "affiliationType": "militaryMember", "fileNames": ["military-waiver.pdf"], "status": "active", From 844aaea459101c5e5fc79263df4941de0f7bcafa Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Fri, 26 Dec 2025 16:36:19 -0700 Subject: [PATCH 22/53] fix remaining tests to use new format --- .../lambdas/python/common/common_test/test_data_generator.py | 2 +- .../function/test_data_model/test_provider_transformations.py | 2 +- .../tests/function/test_handlers/test_providers.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py b/backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py index b70e3dd05..e4fb39ffc 100644 --- a/backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py +++ b/backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py @@ -221,7 +221,7 @@ def generate_default_military_affiliation(value_overrides: dict | None = None) - 'compact': DEFAULT_COMPACT, 'type': MILITARY_AFFILIATION_RECORD_TYPE, 'documentKeys': [ - f'/provider/{DEFAULT_PROVIDER_ID}/document-type/military-affiliations/{DEFAULT_PROVIDER_UPDATE_DATETIME.split("T")[0]}/1234#military-waiver.pdf' + f'/provider/{DEFAULT_PROVIDER_ID}/document-type/military-affiliations/{DEFAULT_PROVIDER_UPDATE_DATETIME}/1234#military-waiver.pdf' ], 'fileNames': ['military-waiver.pdf'], 'affiliationType': DEFAULT_MILITARY_AFFILIATION_TYPE, diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_data_model/test_provider_transformations.py b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_data_model/test_provider_transformations.py index d8a1d2577..b930f4b6f 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_data_model/test_provider_transformations.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_data_model/test_provider_transformations.py @@ -134,7 +134,7 @@ def test_transformations(self, mock_license_preprocessing_queue): affiliation_type=MilitaryAffiliationType.MILITARY_MEMBER, file_names=['military-waiver.pdf'], document_keys=[ - f'/provider/{provider_id}/document-type/military-affiliations/2024-07-08/1234#military-waiver.pdf' + f'/provider/{provider_id}/document-type/military-affiliations/2024-07-08T23:59:59+00:00/1234#military-waiver.pdf' ], ) diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_providers.py b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_providers.py index c137017e3..396b99fd1 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_providers.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_providers.py @@ -393,7 +393,7 @@ def test_get_provider_returns_expected_download_links_when_caller_is_compact_adm # we can't assert on the whole url, since it changes with time # we can verify the path to the file matches expected values self.assertIn( - 'https://provider-user-bucket.s3.amazonaws.com//provider/89a6377e-c3a5-40e5-bca5-317ec854c570/document-type/military-affiliations/2024-07-08/1234%23military-waiver.pdf', + 'https://provider-user-bucket.s3.amazonaws.com//provider/89a6377e-c3a5-40e5-bca5-317ec854c570/document-type/military-affiliations/2024-07-08T23%3A59%3A59%2B00%3A00/1234%23military-waiver.pdf', provider_data['militaryAffiliations'][0]['downloadLinks'][0]['url'], ) From ad08a64ab5013a2d712055fe8432304e500e60b2 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Mon, 29 Dec 2025 14:46:25 -0700 Subject: [PATCH 23/53] check top level military status field in privilege purchase logic --- .../data_model/schema/provider/__init__.py | 4 +- .../python/purchases/handlers/privileges.py | 14 +++++-- .../test_handlers/test_purchase_privileges.py | 42 ++++++++++++++----- 3 files changed, 44 insertions(+), 16 deletions(-) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/__init__.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/__init__.py index fc989c689..2c4b5a13f 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/__init__.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/__init__.py @@ -148,7 +148,7 @@ def currentHomeJurisdiction(self) -> str | None: return self._data.get('currentHomeJurisdiction') @property - def militaryStatus(self) -> str: + def militaryStatus(self) -> str | None: """ The military audit status of the provider. @@ -158,7 +158,7 @@ def militaryStatus(self) -> str: return self._data.get('militaryStatus') @property - def militaryStatusNote(self) -> str: + def militaryStatusNote(self) -> str | None: """ The note from the most recent military audit decision (if declined). diff --git a/backend/compact-connect/lambdas/python/purchases/handlers/privileges.py b/backend/compact-connect/lambdas/python/purchases/handlers/privileges.py index 8ef3d0cde..7a19ac4a4 100644 --- a/backend/compact-connect/lambdas/python/purchases/handlers/privileges.py +++ b/backend/compact-connect/lambdas/python/purchases/handlers/privileges.py @@ -9,7 +9,7 @@ CompactEligibilityStatus, HomeJurisdictionChangeStatusEnum, LicenseDeactivatedStatusEnum, - LicenseEncumberedStatusEnum, + LicenseEncumberedStatusEnum, MilitaryAuditStatus, ) from cc_common.data_model.schema.compact import Compact from cc_common.data_model.schema.compact.api import CompactOptionsResponseSchema @@ -339,8 +339,9 @@ def post_purchase_privileges(event: dict, context: LambdaContext): # noqa: ARG0 ) license_expiration_date: date = matching_license_record.dateOfExpiration - provider_latest_military_status = provider_user_records.get_latest_military_affiliation_status() - if provider_latest_military_status == MilitaryAffiliationStatus.INITIALIZING: + # make sure latest military file upload was successful + provider_latest_military_upload_status = provider_user_records.get_latest_military_affiliation_status() + if provider_latest_military_upload_status == MilitaryAffiliationStatus.INITIALIZING: # this only occurs if the user's military document was not processed by S3 as expected raise CCInvalidRequestException( 'Your proof of military affiliation documentation was not successfully processed. ' @@ -348,7 +349,12 @@ def post_purchase_privileges(event: dict, context: LambdaContext): # noqa: ARG0 'documentation or end your military affiliation.' ) - user_active_military = provider_latest_military_status == MilitaryAffiliationStatus.ACTIVE + # Get militaryStatus from the provider record + # militaryStatus defaults to 'notApplicable' if not present (set in generate_api_response_object) + provider_military_status = top_level_provider_record.militaryStatus or MilitaryAuditStatus.NOT_APPLICABLE + + # User is considered active military if their militaryStatus is either 'tentative' or 'approved' + user_active_military = provider_military_status in (MilitaryAuditStatus.TENTATIVE, MilitaryAuditStatus.APPROVED) # Validate attestations are the latest versions before proceeding with the purchase _validate_attestations(compact_abbr, body.get('attestations', []), user_active_military) diff --git a/backend/compact-connect/lambdas/python/purchases/tests/function/test_handlers/test_purchase_privileges.py b/backend/compact-connect/lambdas/python/purchases/tests/function/test_handlers/test_purchase_privileges.py index 3008029b3..0cbf184c3 100644 --- a/backend/compact-connect/lambdas/python/purchases/tests/function/test_handlers/test_purchase_privileges.py +++ b/backend/compact-connect/lambdas/python/purchases/tests/function/test_handlers/test_purchase_privileges.py @@ -300,10 +300,10 @@ def test_post_purchase_privileges_forbidden_with_recent_encumbrance(self, mock_p resp = post_purchase_privileges(event, self.mock_context) self.assertEqual(400, resp['statusCode']) - def _when_testing_military_affiliation_status( + def _when_testing_military_status( self, mock_purchase_client_constructor: MagicMock, - military_affiliation_status: str, + military_status: str, expected_military_parameter: bool, ): from handlers.privileges import post_purchase_privileges @@ -312,8 +312,11 @@ def _when_testing_military_affiliation_status( mock_purchase_client_constructor ) event = self._when_testing_provider_user_event_with_custom_claims() - self._load_military_affiliation_record_data(status=military_affiliation_status) - attestations = generate_default_attestation_list(active_military=military_affiliation_status == 'active') + # Set militaryStatus on the provider record + self.test_data_generator.put_default_provider_record_in_provider_table( + value_overrides={'militaryStatus': military_status, 'providerId': TEST_PROVIDER_ID} + ) + attestations = generate_default_attestation_list(active_military=expected_military_parameter) event['body'] = _generate_test_request_body(attestations=attestations) @@ -324,16 +327,32 @@ def _when_testing_military_affiliation_status( self.assertEqual(expected_military_parameter, purchase_client_call_kwargs['user_active_military']) @patch('handlers.privileges.PurchaseClient') - def test_post_purchase_privileges_calls_purchase_client_with_active_military_status( + def test_post_purchase_privileges_calls_purchase_client_with_tentative_military_status( + self, mock_purchase_client_constructor + ): + """Test that tentative military status is considered active military.""" + self._when_testing_military_status(mock_purchase_client_constructor, 'tentative', True) + + @patch('handlers.privileges.PurchaseClient') + def test_post_purchase_privileges_calls_purchase_client_with_approved_military_status( self, mock_purchase_client_constructor ): - self._when_testing_military_affiliation_status(mock_purchase_client_constructor, 'active', True) + """Test that approved military status is considered active military.""" + self._when_testing_military_status(mock_purchase_client_constructor, 'approved', True) @patch('handlers.privileges.PurchaseClient') - def test_post_purchase_privileges_calls_purchase_client_with_inactive_military_status( + def test_post_purchase_privileges_calls_purchase_client_with_declined_military_status( self, mock_purchase_client_constructor ): - self._when_testing_military_affiliation_status(mock_purchase_client_constructor, 'inactive', False) + """Test that declined military status is not considered active military.""" + self._when_testing_military_status(mock_purchase_client_constructor, 'declined', False) + + @patch('handlers.privileges.PurchaseClient') + def test_post_purchase_privileges_calls_purchase_client_with_not_applicable_military_status( + self, mock_purchase_client_constructor + ): + """Test that notApplicable military status is not considered active military.""" + self._when_testing_military_status(mock_purchase_client_constructor, 'notApplicable', False) @patch('handlers.privileges.PurchaseClient') def test_post_purchase_privileges_raises_exception_when_military_affiliation_in_initializing_status( @@ -948,14 +967,17 @@ def test_post_purchase_privileges_validates_investigation_attestations(self, moc @patch('handlers.privileges.PurchaseClient') def test_post_purchase_privileges_validates_military_attestation(self, mock_purchase_client_constructor): - """Test that military attestation is required when user has active military affiliation.""" + """Test that military attestation is required when user has active military status (tentative or approved).""" from handlers.privileges import post_purchase_privileges self._when_purchase_client_successfully_processes_request(mock_purchase_client_constructor) - self._load_military_affiliation_record_data(status='active') event = self._when_testing_provider_user_event_with_custom_claims() event['body'] = _generate_test_request_body() + # Set militaryStatus to 'approved' on the provider record + self.test_data_generator.put_default_provider_record_in_provider_table( + value_overrides={'militaryStatus': 'approved', 'providerId': TEST_PROVIDER_ID} + ) resp = post_purchase_privileges(event, self.mock_context) self.assertEqual(400, resp['statusCode'], resp['body']) From bc45b1b23937d3223f8bd8a5a991f5f7a95e0661 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Mon, 29 Dec 2025 15:13:54 -0700 Subject: [PATCH 24/53] linter/formatting --- .../python/data-events/handlers/military_audit_events.py | 4 +++- .../lambdas/python/purchases/handlers/privileges.py | 3 ++- .../tests/smoke/military_affiliation_smoke_tests.py | 3 +-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py b/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py index d51ad5cf2..78fff1ebd 100644 --- a/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py +++ b/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py @@ -66,7 +66,9 @@ def military_audit_notification_listener(message: dict, tracker: NotificationTra # Determine event type and send appropriate notification event_type = ( - EventType.MILITARY_AUDIT_APPROVED if audit_result == MilitaryAuditStatus.APPROVED else EventType.MILITARY_AUDIT_DECLINED + EventType.MILITARY_AUDIT_APPROVED + if audit_result == MilitaryAuditStatus.APPROVED + else EventType.MILITARY_AUDIT_DECLINED ) try: diff --git a/backend/compact-connect/lambdas/python/purchases/handlers/privileges.py b/backend/compact-connect/lambdas/python/purchases/handlers/privileges.py index 7a19ac4a4..f734652a1 100644 --- a/backend/compact-connect/lambdas/python/purchases/handlers/privileges.py +++ b/backend/compact-connect/lambdas/python/purchases/handlers/privileges.py @@ -9,7 +9,8 @@ CompactEligibilityStatus, HomeJurisdictionChangeStatusEnum, LicenseDeactivatedStatusEnum, - LicenseEncumberedStatusEnum, MilitaryAuditStatus, + LicenseEncumberedStatusEnum, + MilitaryAuditStatus, ) from cc_common.data_model.schema.compact import Compact from cc_common.data_model.schema.compact.api import CompactOptionsResponseSchema diff --git a/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py b/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py index 195d0b317..8811a468b 100644 --- a/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py +++ b/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py @@ -109,8 +109,7 @@ def test_military_affiliation_upload(): raise SmokeTestFailureException('No military affiliations found in provider data') # Find the military affiliation record with the most recent dateOfUpload matching_military_affiliation = max( - military_affiliations, - key=lambda ma: datetime.fromisoformat(ma['dateOfUpload']) + military_affiliations, key=lambda ma: datetime.fromisoformat(ma['dateOfUpload']) ) if not matching_military_affiliation: raise SmokeTestFailureException( From 2419d0cad9ae7010f2382a2312fafef1ecb03eb9 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Mon, 29 Dec 2025 15:20:44 -0700 Subject: [PATCH 25/53] PR cleanup --- .../lambdas/python/common/cc_common/data_model/data_client.py | 2 +- .../tests/smoke/military_affiliation_smoke_tests.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py index e61d87017..e439e450a 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py @@ -964,7 +964,7 @@ def process_military_audit( military_status_note: str | None = None, ) -> None: """ - Update provider and latest military affiliation with audit result in a transaction. + Update provider with audit result in a transaction. This method: 1. Gets the provider record diff --git a/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py b/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py index 8811a468b..8d1038496 100644 --- a/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py +++ b/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py @@ -2,7 +2,7 @@ #!/usr/bin/env python3 import os import time -from datetime import UTC, datetime +from datetime import datetime import requests from smoke_common import ( From e13520356f36276d84667a63062d9829ecc65d48 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Mon, 29 Dec 2025 15:41:54 -0700 Subject: [PATCH 26/53] Update API/Postman spec to latest --- .../internal/postman/postman-collection.json | 450 ++++++++++++------ 1 file changed, 294 insertions(+), 156 deletions(-) diff --git a/backend/compact-connect/docs/internal/postman/postman-collection.json b/backend/compact-connect/docs/internal/postman/postman-collection.json index 5c35dd33a..adfde0af9 100644 --- a/backend/compact-connect/docs/internal/postman/postman-collection.json +++ b/backend/compact-connect/docs/internal/postman/postman-collection.json @@ -10,7 +10,7 @@ "type": "bearer" }, "info": { - "_postman_id": "7cb454e4-7ab1-426a-9dda-b7b0ed228704", + "_postman_id": "a9827ea4-b2f0-4c83-8c75-4c3baed1ee43", "description": { "content": "", "type": "text/plain" @@ -401,7 +401,7 @@ "item": [ { "event": [], - "id": "c1b1d849-13b2-4f67-baac-6234dcd7b77c", + "id": "ab5f5ee5-b84d-457d-a992-f11aaaf1234b", "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 \"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\": \"nh\"\n },\n {\n \"isLive\": \"\",\n \"postalAbbreviation\": \"nv\"\n }\n ],\n \"licenseeRegistrationEnabled\": \"\",\n \"transactionFeeConfiguration\": {\n \"licenseeCharges\": {\n \"active\": \"\",\n \"chargeAmount\": \"\",\n \"chargeType\": \"FLAT_FEE_PER_PRIVILEGE\"\n }\n }\n}", "code": 200, "cookie": [], "header": [ @@ -453,7 +453,7 @@ "value": "application/json" } ], - "id": "3cd40a5c-9778-49c5-ac94-0a9bc9d477c5", + "id": "ffc3123b-50a8-4c36-8079-4701da829fb2", "name": "200 response", "originalRequest": { "body": {}, @@ -491,7 +491,7 @@ }, { "event": [], - "id": "3568af2c-3a8b-4a0a-992a-f90b1f5b6685", + "id": "38fbb806-4978-4f2f-a698-dfb90e5d465b", "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 \"compactCommissionFee\": {\n \"feeAmount\": \"\",\n \"feeType\": \"FLAT_RATE\"\n },\n \"compactOperationsTeamEmails\": [\n \"\"\n ],\n \"compactSummaryReportNotificationEmails\": [\n \"\"\n ],\n \"configuredStates\": [\n {\n \"isLive\": \"\",\n \"postalAbbreviation\": \"vi\"\n },\n {\n \"isLive\": \"\",\n \"postalAbbreviation\": \"ne\"\n }\n ],\n \"licenseeRegistrationEnabled\": \"\",\n \"transactionFeeConfiguration\": {\n \"licenseeCharges\": {\n \"active\": \"\",\n \"chargeAmount\": \"\",\n \"chargeType\": \"FLAT_FEE_PER_PRIVILEGE\"\n }\n }\n}" }, "description": {}, "header": [ @@ -556,7 +556,7 @@ "value": "application/json" } ], - "id": "f50a3dc3-9641-4ae0-8050-d33c19c7970e", + "id": "2e16ec66-8d31-4670-acf7-68d10898c562", "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 \"compactCommissionFee\": {\n \"feeAmount\": \"\",\n \"feeType\": \"FLAT_RATE\"\n },\n \"compactOperationsTeamEmails\": [\n \"\"\n ],\n \"compactSummaryReportNotificationEmails\": [\n \"\"\n ],\n \"configuredStates\": [\n {\n \"isLive\": \"\",\n \"postalAbbreviation\": \"vi\"\n },\n {\n \"isLive\": \"\",\n \"postalAbbreviation\": \"ne\"\n }\n ],\n \"licenseeRegistrationEnabled\": \"\",\n \"transactionFeeConfiguration\": {\n \"licenseeCharges\": {\n \"active\": \"\",\n \"chargeAmount\": \"\",\n \"chargeType\": \"FLAT_FEE_PER_PRIVILEGE\"\n }\n }\n}" }, "header": [ { @@ -613,7 +613,7 @@ "item": [ { "event": [], - "id": "e1d7d1b3-0caa-44e1-aab8-0b0d9520f54d", + "id": "bc504781-860a-4391-b086-bd8fd22bf801", "name": "/v1/compacts/:compact/attestations/:attestationId", "protocolProfileBehavior": { "disableBodyPruning": true @@ -668,7 +668,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"dateCreated\": \"\",\n \"attestationId\": \"\",\n \"compact\": \"aslp\",\n \"text\": \"\",\n \"type\": \"attestation\",\n \"locale\": \"\",\n \"version\": \"\",\n \"required\": \"\"\n}", + "body": "{\n \"dateCreated\": \"\",\n \"attestationId\": \"\",\n \"compact\": \"octp\",\n \"text\": \"\",\n \"type\": \"attestation\",\n \"locale\": \"\",\n \"version\": \"\",\n \"required\": \"\"\n}", "code": 200, "cookie": [], "header": [ @@ -677,7 +677,7 @@ "value": "application/json" } ], - "id": "c61b5da2-6143-456f-80ba-71cd6300bbcd", + "id": "3883dec3-f380-4382-bc17-8a0a32ee0a5d", "name": "200 response", "originalRequest": { "body": {}, @@ -729,7 +729,7 @@ "item": [ { "event": [], - "id": "784d7a36-d743-4499-83a8-9a35635bd4f7", + "id": "ae3ce956-63ee-44af-ba2c-50180670db85", "name": "/v1/compacts/:compact/credentials/payment-processor", "protocolProfileBehavior": { "disableBodyPruning": true @@ -796,7 +796,7 @@ "value": "application/json" } ], - "id": "49cc093e-9b56-4bef-9ade-555b3398672b", + "id": "e91a1a84-aaea-4e1b-b10b-31b7e5b3d33d", "name": "200 response", "originalRequest": { "body": { @@ -858,7 +858,7 @@ "item": [ { "event": [], - "id": "edd82d48-8d82-4b01-9b7a-248727deabee", + "id": "d7f4cc66-6f21-40b1-923d-ce2b92fe9b11", "name": "/v1/compacts/:compact/jurisdictions", "protocolProfileBehavior": { "disableBodyPruning": true @@ -911,7 +911,7 @@ "value": "application/json" } ], - "id": "a16b33dd-17fe-4be5-91da-8bbc43fbe565", + "id": "df4850e4-8054-4f5e-8eee-3a388688281a", "name": "200 response", "originalRequest": { "body": {}, @@ -984,7 +984,7 @@ } } ], - "id": "19fef01d-8597-415b-80d2-16cc5c6b328f", + "id": "f8d50a91-df46-459a-a12c-653da43e468b", "name": "/v1/compacts/:compact/jurisdictions/:jurisdiction/licenses/bulk-upload", "protocolProfileBehavior": { "disableBodyPruning": true @@ -1041,7 +1041,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"upload\": {\n \"fields\": {\n \"key_0\": \"\"\n },\n \"url\": \"\"\n }\n}", + "body": "{\n \"upload\": {\n \"fields\": {\n \"consequat5b\": \"\"\n },\n \"url\": \"\"\n }\n}", "code": 200, "cookie": [], "header": [ @@ -1050,7 +1050,7 @@ "value": "application/json" } ], - "id": "02ec708c-bdb8-4064-b7fd-37a790997853", + "id": "f0bf2b8d-561b-4c50-94a4-0c08cd7e946a", "name": "200 response", "originalRequest": { "body": {}, @@ -1110,7 +1110,7 @@ "item": [ { "event": [], - "id": "2974d272-c532-41ce-91cc-90f24da34f00", + "id": "d85990e7-6376-4477-9b06-f4b17c5c4489", "name": "/v1/compacts/:compact/providers/query", "protocolProfileBehavior": { "disableBodyPruning": true @@ -1124,7 +1124,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\": \"8ab484ca-2bcd-4109-8bd1-732bba4947b7\",\n \"jurisdiction\": \"in\",\n \"givenName\": \"\",\n \"familyName\": \"\"\n },\n \"pagination\": {\n \"lastKey\": \"\",\n \"pageSize\": \"\"\n },\n \"sorting\": {\n \"key\": \"dateOfUpdate\",\n \"direction\": \"ascending\"\n }\n}" }, "description": {}, "header": [ @@ -1168,7 +1168,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\": \"07-22\",\n \"compact\": \"octp\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"1187-11-07\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"licenseJurisdiction\": \"dc\",\n \"licenseStatus\": \"active\",\n \"privilegeJurisdictions\": [\n \"mn\",\n \"hi\"\n ],\n \"providerId\": \"a44b4853-1ddc-4a62-bb05-b7eae177385e\",\n \"type\": \"provider\",\n \"npi\": \"1914779016\",\n \"dateOfBirth\": \"1690-08-22\",\n \"suffix\": \"\",\n \"currentHomeJurisdiction\": \"ky\",\n \"ssnLastFour\": \"1423\",\n \"middleName\": \"\",\n \"compactConnectRegisteredEmailAddress\": \"\"\n },\n {\n \"birthMonthDay\": \"04-02\",\n \"compact\": \"aslp\",\n \"compactEligibility\": \"eligible\",\n \"dateOfExpiration\": \"2638-07-15\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"licenseJurisdiction\": \"id\",\n \"licenseStatus\": \"active\",\n \"privilegeJurisdictions\": [\n \"ar\",\n \"al\"\n ],\n \"providerId\": \"7bf12bd6-36ad-40ae-aa00-b7738968348a\",\n \"type\": \"provider\",\n \"npi\": \"9826340686\",\n \"dateOfBirth\": \"2326-11-31\",\n \"suffix\": \"\",\n \"currentHomeJurisdiction\": \"pr\",\n \"ssnLastFour\": \"9693\",\n \"middleName\": \"\",\n \"compactConnectRegisteredEmailAddress\": \"\"\n }\n ],\n \"sorting\": {\n \"key\": \"dateOfUpdate\",\n \"direction\": \"ascending\"\n }\n}", "code": 200, "cookie": [], "header": [ @@ -1177,7 +1177,7 @@ "value": "application/json" } ], - "id": "3c87e5d0-6363-4641-8a9a-681c6a5e6f21", + "id": "dc58ec63-fd5f-456e-873f-f7dd415097ab", "name": "200 response", "originalRequest": { "body": { @@ -1188,7 +1188,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\": \"8ab484ca-2bcd-4109-8bd1-732bba4947b7\",\n \"jurisdiction\": \"in\",\n \"givenName\": \"\",\n \"familyName\": \"\"\n },\n \"pagination\": {\n \"lastKey\": \"\",\n \"pageSize\": \"\"\n },\n \"sorting\": {\n \"key\": \"dateOfUpdate\",\n \"direction\": \"ascending\"\n }\n}" }, "header": [ { @@ -1236,7 +1236,7 @@ "item": [ { "event": [], - "id": "afbfff17-6d80-4e06-bd96-42ab7d331349", + "id": "07989aa5-de58-4467-bb09-a81ac12a5d07", "name": "/v1/compacts/:compact/providers/:providerId", "protocolProfileBehavior": { "disableBodyPruning": true @@ -1291,7 +1291,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\": \"11-17\",\n \"compact\": \"aslp\",\n \"dateOfExpiration\": \"1016-04-15\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"licenseJurisdiction\": \"mt\",\n \"licenses\": [\n {\n \"compact\": \"octp\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"2174-01-13\",\n \"dateOfIssuance\": \"2125-08-07\",\n \"dateOfRenewal\": \"2884-03-30\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"history\": [\n {\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"ok\",\n \"previous\": {\n \"dateOfExpiration\": \"2765-01-27\",\n \"dateOfIssuance\": \"2200-04-07\",\n \"dateOfRenewal\": \"1960-12-31\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"4323861062\",\n \"compactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2605-04-10\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+876228646884801\",\n \"licenseStatus\": \"active\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"licenseDeactivation\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"speech-language pathologist\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"npi\": \"5362356702\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2573-10-02\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"1792-01-14\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"1556-01-31\",\n \"phoneNumber\": \"+04732654\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"1887-10-03\",\n \"licenseStatus\": \"active\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n },\n {\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"in\",\n \"previous\": {\n \"dateOfExpiration\": \"2136-12-01\",\n \"dateOfIssuance\": \"1696-01-31\",\n \"dateOfRenewal\": \"1964-10-31\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"4155100534\",\n \"compactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2824-07-03\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+38146683\",\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\": \"8589103256\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1671-10-13\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2218-12-11\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"2539-10-14\",\n \"phoneNumber\": \"+86007188095\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"1897-01-08\",\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\": \"mn\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"licenseStatus\": \"inactive\",\n \"licenseType\": \"occupational therapy assistant\",\n \"middleName\": \"\",\n \"providerId\": \"cfdbcbbc-6c72-4deb-8651-2fd9915f5573\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"investigations\": [\n {\n \"compact\": \"coun\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"me\",\n \"licenseType\": \"\",\n \"providerId\": \"63b94041-8f0b-43c5-971a-478833b5d6e6\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"coun\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"ny\",\n \"licenseType\": \"\",\n \"providerId\": \"969c9a49-d226-443b-aaa1-dcd93df0300a\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"licenseNumber\": \"\",\n \"investigationStatus\": \"underInvestigation\",\n \"npi\": \"9667390556\",\n \"dateOfBirth\": \"2014-10-28\",\n \"ssnLastFour\": \"5961\",\n \"phoneNumber\": \"+215555457776743\",\n \"licenseStatusName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"octp\",\n \"creationDate\": \"2979-12-30\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2803-07-04\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"me\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"ea82b231-5b5a-41a1-bd61-170eda210d44\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1792-11-04\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"aslp\",\n \"creationDate\": \"2045-12-16\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2301-05-09\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"id\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"8d7a80d6-0190-4b0a-8563-1fb067203716\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1125-04-31\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n }\n ]\n },\n {\n \"compact\": \"octp\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"1160-11-31\",\n \"dateOfIssuance\": \"2576-11-31\",\n \"dateOfRenewal\": \"2046-09-25\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"history\": [\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"md\",\n \"previous\": {\n \"dateOfExpiration\": \"1355-01-03\",\n \"dateOfIssuance\": \"2864-04-16\",\n \"dateOfRenewal\": \"1651-04-11\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"7235513969\",\n \"compactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2108-12-30\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+9730063408781\",\n \"licenseStatus\": \"active\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"expiration\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"speech-language pathologist\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"npi\": \"4979399694\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1169-04-20\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2610-03-30\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"1235-06-10\",\n \"phoneNumber\": \"+0624416018\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"2120-08-29\",\n \"licenseStatus\": \"active\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n },\n {\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"mt\",\n \"previous\": {\n \"dateOfExpiration\": \"2247-12-07\",\n \"dateOfIssuance\": \"1732-04-07\",\n \"dateOfRenewal\": \"2490-01-20\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"4954399462\",\n \"compactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1542-12-01\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+06725848936\",\n \"licenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"emailChange\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"occupational therapy assistant\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"npi\": \"1595699795\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2091-12-09\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2418-05-05\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"2702-10-02\",\n \"phoneNumber\": \"+37788018735632\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"1105-10-31\",\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\": \"ny\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"licensed professional counselor\",\n \"middleName\": \"\",\n \"providerId\": \"313794c2-e6a4-40a2-9e20-1fd7af3d276d\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"investigations\": [\n {\n \"compact\": \"coun\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"ms\",\n \"licenseType\": \"\",\n \"providerId\": \"f47123b6-8f51-44f3-a1cb-7b1a8e15611f\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"aslp\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"nh\",\n \"licenseType\": \"\",\n \"providerId\": \"9594910b-52b8-4b5d-985b-9750832cd1c0\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"licenseNumber\": \"\",\n \"investigationStatus\": \"underInvestigation\",\n \"npi\": \"2736368753\",\n \"dateOfBirth\": \"2164-07-02\",\n \"ssnLastFour\": \"4355\",\n \"phoneNumber\": \"+70372285\",\n \"licenseStatusName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"aslp\",\n \"creationDate\": \"2310-07-04\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2892-12-05\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"ia\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"6cabca4e-7fbe-4e81-9a6c-8e4cfb4bd7df\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2524-12-31\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"octp\",\n \"creationDate\": \"1388-04-19\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2079-09-31\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"wa\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"59d6846b-391f-4b14-80fe-41614be8372d\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1284-01-19\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"militaryAffiliations\": [\n {\n \"affiliationType\": \"militaryMemberSpouse\",\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"\",\n \"dateOfUpload\": \"2656-06-30\",\n \"fileNames\": [\n \"\",\n \"\"\n ],\n \"providerId\": \"33ff6892-8ba6-46f7-987e-f1fddba57f4b\",\n \"status\": \"inactive\",\n \"type\": \"militaryAffiliation\",\n \"downloadLinks\": [\n {\n \"fileName\": \"\",\n \"url\": \"\"\n },\n {\n \"fileName\": \"\",\n \"url\": \"\"\n }\n ]\n },\n {\n \"affiliationType\": \"militaryMember\",\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"\",\n \"dateOfUpload\": \"2442-01-07\",\n \"fileNames\": [\n \"\",\n \"\"\n ],\n \"providerId\": \"959e6e13-9d35-4d46-8091-12d3cb827e45\",\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 \"md\",\n \"nc\"\n ],\n \"privileges\": [\n {\n \"administratorSetStatus\": \"active\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compact\": \"octp\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1102-12-12\",\n \"dateOfIssuance\": \"1313-12-05\",\n \"dateOfRenewal\": \"2701-05-30\",\n \"dateOfUpdate\": \"\",\n \"history\": [\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"in\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"2836-07-27\",\n \"dateOfIssuance\": \"2788-10-30\",\n \"dateOfRenewal\": \"1280-10-31\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"id\",\n \"privilegeId\": \"\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"ok\",\n \"type\": \"privilege\",\n \"providerId\": \"a8431870-2a46-424f-9be6-9d9612adc5bd\",\n \"status\": \"inactive\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"expiration\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"speech-language pathologist\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"ok\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"mn\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"1612-12-31\",\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"2400-02-30\",\n \"privilegeId\": \"\",\n \"providerId\": \"040e7e14-5972-4398-95ee-399a7036cfb7\",\n \"dateOfRenewal\": \"2756-10-09\",\n \"dateOfUpdate\": \"\",\n \"status\": \"inactive\"\n }\n },\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"fl\",\n \"previous\": {\n \"administratorSetStatus\": \"active\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"2891-04-07\",\n \"dateOfIssuance\": \"1522-12-30\",\n \"dateOfRenewal\": \"1235-10-15\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"oh\",\n \"privilegeId\": \"\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"ny\",\n \"type\": \"privilege\",\n \"providerId\": \"8174175e-978b-46f6-b0d5-75dc883e83ea\",\n \"status\": \"active\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"deactivation\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"speech-language pathologist\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"tn\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"ut\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"1015-11-16\",\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"2808-10-30\",\n \"privilegeId\": \"\",\n \"providerId\": \"402bc3a2-4a76-49df-a95e-83cdc8d4c6c9\",\n \"dateOfRenewal\": \"2912-11-05\",\n \"dateOfUpdate\": \"\",\n \"status\": \"inactive\"\n }\n }\n ],\n \"jurisdiction\": \"il\",\n \"licenseJurisdiction\": \"ok\",\n \"licenseType\": \"licensed professional counselor\",\n \"privilegeId\": \"\",\n \"providerId\": \"fabef33a-bb76-469e-9794-0fb12dabb9d2\",\n \"status\": \"inactive\",\n \"type\": \"privilege\",\n \"investigationStatus\": \"underInvestigation\",\n \"investigations\": [\n {\n \"compact\": \"coun\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"nc\",\n \"licenseType\": \"\",\n \"providerId\": \"b8939464-4b19-4a6d-bc77-5bfd9d0d7ea1\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"aslp\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"az\",\n \"licenseType\": \"\",\n \"providerId\": \"aef09a8b-757f-4a4d-9739-12dfaac27da4\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"1589-10-10\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1165-04-31\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"ca\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"deacc877-04f8-425a-85af-261178c514ae\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1861-12-25\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"2914-08-03\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2120-04-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"id\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"cf4315a9-ccdb-42a8-854b-7e90def7885a\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1914-04-30\",\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\": \"octp\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1723-06-11\",\n \"dateOfIssuance\": \"1203-12-15\",\n \"dateOfRenewal\": \"2871-11-10\",\n \"dateOfUpdate\": \"\",\n \"history\": [\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"nj\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1058-12-11\",\n \"dateOfIssuance\": \"2149-11-30\",\n \"dateOfRenewal\": \"1018-08-01\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"fl\",\n \"privilegeId\": \"\",\n \"compact\": \"coun\",\n \"jurisdiction\": \"mi\",\n \"type\": \"privilege\",\n \"providerId\": \"5e722037-8d03-47ab-afaa-d915b0cac757\",\n \"status\": \"inactive\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"lifting_encumbrance\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"occupational therapy assistant\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"tn\",\n \"compact\": \"coun\",\n \"jurisdiction\": \"wi\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"1426-03-06\",\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"1199-10-06\",\n \"privilegeId\": \"\",\n \"providerId\": \"283c9149-73e8-40a4-9c6a-788e01bbed79\",\n \"dateOfRenewal\": \"1271-04-30\",\n \"dateOfUpdate\": \"\",\n \"status\": \"inactive\"\n }\n },\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"id\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"2341-12-30\",\n \"dateOfIssuance\": \"1576-03-30\",\n \"dateOfRenewal\": \"1709-03-03\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"wy\",\n \"privilegeId\": \"\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"ut\",\n \"type\": \"privilege\",\n \"providerId\": \"366e661a-421d-4689-b899-793f7233747c\",\n \"status\": \"inactive\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"other\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"occupational therapist\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"hi\",\n \"compact\": \"coun\",\n \"jurisdiction\": \"ky\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"2939-01-02\",\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"1022-10-24\",\n \"privilegeId\": \"\",\n \"providerId\": \"fbbe0975-c404-46f0-9dc7-399ffeae3683\",\n \"dateOfRenewal\": \"1236-01-31\",\n \"dateOfUpdate\": \"\",\n \"status\": \"active\"\n }\n }\n ],\n \"jurisdiction\": \"ms\",\n \"licenseJurisdiction\": \"mn\",\n \"licenseType\": \"licensed professional counselor\",\n \"privilegeId\": \"\",\n \"providerId\": \"8d94428c-bb5b-4140-8aa6-066bffffdfdc\",\n \"status\": \"inactive\",\n \"type\": \"privilege\",\n \"investigationStatus\": \"underInvestigation\",\n \"investigations\": [\n {\n \"compact\": \"coun\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"md\",\n \"licenseType\": \"\",\n \"providerId\": \"6b200887-a4e3-4124-acdc-fffce69807f6\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"octp\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"ak\",\n \"licenseType\": \"\",\n \"providerId\": \"3eb7845c-a341-4b11-8db7-6186a3e0f7e9\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"aslp\",\n \"creationDate\": \"2881-04-31\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1128-04-14\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"in\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"c38f6c41-d865-4c37-ae18-63ce5fd18c55\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2871-05-08\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"2484-04-31\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2979-11-04\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"fl\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"be457ff9-9a51-404b-9d23-b9f5eaa9d6c6\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2146-10-05\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"providerId\": \"098edacb-8ab3-4f0b-a655-c9f26be8827c\",\n \"type\": \"provider\",\n \"npi\": \"5990059533\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1157-07-30\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"suffix\": \"\",\n \"currentHomeJurisdiction\": \"wi\",\n \"ssnLastFour\": \"7344\",\n \"licenseStatus\": \"active\",\n \"middleName\": \"\",\n \"compactConnectRegisteredEmailAddress\": \"\"\n}", "code": 200, "cookie": [], "header": [ @@ -1300,7 +1300,7 @@ "value": "application/json" } ], - "id": "b7cc6c87-9d25-473b-81fd-e94664497fc3", + "id": "d8acfec2-366f-4502-8a64-a174dfacbbf3", "name": "200 response", "originalRequest": { "body": {}, @@ -1358,7 +1358,7 @@ "item": [ { "event": [], - "id": "c8eaee6b-941a-4ba6-b5c2-86650cb9d0c7", + "id": "698fbcbc-7384-411d-902b-7be069c3c4cb", "name": "/v1/compacts/:compact/providers/:providerId/licenses/jurisdiction/:jurisdiction/licenseType/:licenseType/encumbrance", "protocolProfileBehavior": { "disableBodyPruning": true @@ -1372,7 +1372,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 \"encumbranceEffectiveDate\": \"1797-02-10\",\n \"encumbranceType\": \"denial\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"clinicalPrivilegeActionCategory\": \"\"\n}" }, "description": {}, "header": [ @@ -1461,7 +1461,7 @@ "value": "application/json" } ], - "id": "1d4911ea-8ff0-4972-89ec-1328421603d7", + "id": "0df0c72a-6727-485e-8e43-227004e11c03", "name": "200 response", "originalRequest": { "body": { @@ -1472,7 +1472,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 \"encumbranceEffectiveDate\": \"1797-02-10\",\n \"encumbranceType\": \"denial\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"clinicalPrivilegeActionCategory\": \"\"\n}" }, "header": [ { @@ -1523,7 +1523,7 @@ "item": [ { "event": [], - "id": "9cd0e9d6-8c28-4bd0-a03c-e498f58bd8d3", + "id": "f3a68b3b-b1cf-47e4-89b7-fa930d57b7c9", "name": "/v1/compacts/:compact/providers/:providerId/licenses/jurisdiction/:jurisdiction/licenseType/:licenseType/encumbrance/:encumbranceId", "protocolProfileBehavior": { "disableBodyPruning": true @@ -1537,7 +1537,7 @@ "language": "json" } }, - "raw": "{\n \"effectiveLiftDate\": \"1104-03-15\"\n}" + "raw": "{\n \"effectiveLiftDate\": \"1474-11-02\"\n}" }, "description": {}, "header": [ @@ -1637,7 +1637,7 @@ "value": "application/json" } ], - "id": "83eea677-31cb-483b-a142-c5017facf781", + "id": "82314686-ff4e-47ac-83cb-70ea094869d1", "name": "200 response", "originalRequest": { "body": { @@ -1648,7 +1648,7 @@ "language": "json" } }, - "raw": "{\n \"effectiveLiftDate\": \"1104-03-15\"\n}" + "raw": "{\n \"effectiveLiftDate\": \"1474-11-02\"\n}" }, "header": [ { @@ -1706,7 +1706,7 @@ "item": [ { "event": [], - "id": "95b5f3e9-5d82-4af1-9975-01b3fc520a7e", + "id": "75028370-fb6b-4cb9-b73f-1d9b55f90a2c", "name": "/v1/compacts/:compact/providers/:providerId/licenses/jurisdiction/:jurisdiction/licenseType/:licenseType/investigation", "protocolProfileBehavior": { "disableBodyPruning": true @@ -1809,7 +1809,7 @@ "value": "application/json" } ], - "id": "c0b8a977-b11c-4ea6-9e30-cc2d9343e62a", + "id": "dd1b9e80-c2ad-4485-9ae8-40f73e4d478e", "name": "200 response", "originalRequest": { "body": { @@ -1871,7 +1871,7 @@ "item": [ { "event": [], - "id": "bde6e1f5-2b85-42d4-9af4-b096c2990722", + "id": "5d607508-d8ae-4e97-befb-666602d095c6", "name": "/v1/compacts/:compact/providers/:providerId/licenses/jurisdiction/:jurisdiction/licenseType/:licenseType/investigation/:investigationId", "protocolProfileBehavior": { "disableBodyPruning": true @@ -1885,7 +1885,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 \"encumbranceEffectiveDate\": \"1495-02-30\",\n \"encumbranceType\": \"other monitoring\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"clinicalPrivilegeActionCategory\": \"\"\n }\n}" }, "description": {}, "header": [ @@ -1985,7 +1985,7 @@ "value": "application/json" } ], - "id": "6eb0f49a-cccf-469c-a8a6-8a4939443611", + "id": "978639ce-f2ef-4531-98ef-c57a4307677e", "name": "200 response", "originalRequest": { "body": { @@ -1996,7 +1996,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 \"encumbranceEffectiveDate\": \"1495-02-30\",\n \"encumbranceType\": \"other monitoring\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"clinicalPrivilegeActionCategory\": \"\"\n }\n}" }, "header": [ { @@ -2064,6 +2064,144 @@ ], "name": "licenses" }, + { + "description": "", + "item": [ + { + "event": [], + "id": "0ae318b7-2f25-49ba-9b54-d39d5d4b1c46", + "name": "/v1/compacts/:compact/providers/:providerId/militaryAudit", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "body": { + "mode": "raw", + "options": { + "raw": { + "headerFamily": "json", + "language": "json" + } + }, + "raw": "{\n \"militaryStatus\": \"approved\",\n \"militaryStatusNote\": \"\"\n}" + }, + "description": {}, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "PATCH", + "name": "/v1/compacts/:compact/providers/:providerId/militaryAudit", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "providers", + ":providerId", + "militaryAudit" + ], + "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 \"message\": \"\"\n}", + "code": 200, + "cookie": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "id": "ddc3d92d-b2cf-405a-8d55-61a124a69723", + "name": "200 response", + "originalRequest": { + "body": { + "mode": "raw", + "options": { + "raw": { + "headerFamily": "json", + "language": "json" + } + }, + "raw": "{\n \"militaryStatus\": \"approved\",\n \"militaryStatusNote\": \"\"\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", + "militaryAudit" + ], + "query": [], + "variable": [] + } + }, + "status": "OK" + } + ] + } + ], + "name": "militaryAudit" + }, { "description": "", "item": [ @@ -2084,7 +2222,7 @@ "item": [ { "event": [], - "id": "d74f25ec-cfee-4d03-b1f8-d3822c2be6dd", + "id": "33549695-08d8-4fa6-9ec5-4eb6bbd19c4a", "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/deactivate", "protocolProfileBehavior": { "disableBodyPruning": true @@ -2187,7 +2325,7 @@ "value": "application/json" } ], - "id": "cff5ab64-b4a4-4921-8f90-2e694131358f", + "id": "92f1ec6f-ed5f-4364-be09-579b5942d71e", "name": "200 response", "originalRequest": { "body": { @@ -2252,7 +2390,7 @@ "item": [ { "event": [], - "id": "37217a24-2f76-4243-8b84-2864dcff6606", + "id": "79f1c629-6bfb-4182-981d-c43ceb0e0de5", "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/encumbrance", "protocolProfileBehavior": { "disableBodyPruning": true @@ -2266,7 +2404,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 \"encumbranceEffectiveDate\": \"1797-02-10\",\n \"encumbranceType\": \"denial\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"clinicalPrivilegeActionCategory\": \"\"\n}" }, "description": {}, "header": [ @@ -2355,7 +2493,7 @@ "value": "application/json" } ], - "id": "2d0a8450-d518-4f25-adab-9d9c30782586", + "id": "98c7c85b-a66b-4b3b-aa0f-ed3d0bbb7138", "name": "200 response", "originalRequest": { "body": { @@ -2366,7 +2504,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 \"encumbranceEffectiveDate\": \"1797-02-10\",\n \"encumbranceType\": \"denial\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"clinicalPrivilegeActionCategory\": \"\"\n}" }, "header": [ { @@ -2417,7 +2555,7 @@ "item": [ { "event": [], - "id": "76f8edac-8274-4279-9db7-0ff6e00edb27", + "id": "470f9235-c954-42fc-a462-e593fda9ffb0", "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/encumbrance/:encumbranceId", "protocolProfileBehavior": { "disableBodyPruning": true @@ -2431,7 +2569,7 @@ "language": "json" } }, - "raw": "{\n \"effectiveLiftDate\": \"1104-03-15\"\n}" + "raw": "{\n \"effectiveLiftDate\": \"1474-11-02\"\n}" }, "description": {}, "header": [ @@ -2531,7 +2669,7 @@ "value": "application/json" } ], - "id": "16981b5d-ab36-4364-9f2c-a0ec8435e010", + "id": "1686a0f6-5e9b-4f86-9a03-2732152c3aad", "name": "200 response", "originalRequest": { "body": { @@ -2542,7 +2680,7 @@ "language": "json" } }, - "raw": "{\n \"effectiveLiftDate\": \"1104-03-15\"\n}" + "raw": "{\n \"effectiveLiftDate\": \"1474-11-02\"\n}" }, "header": [ { @@ -2600,7 +2738,7 @@ "item": [ { "event": [], - "id": "a3a9a8fc-e963-444d-ad98-28bbd589e0ff", + "id": "9b2de6a9-8058-4576-ab95-da18a3003b16", "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/history", "protocolProfileBehavior": { "disableBodyPruning": true @@ -2681,7 +2819,7 @@ "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}", + "body": "{\n \"compact\": \"octp\",\n \"events\": [\n {\n \"createDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"effectiveDate\": \"1680-11-27\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"encumbrance\",\n \"note\": \"\"\n },\n {\n \"createDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"effectiveDate\": \"2994-12-28\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"other\",\n \"note\": \"\"\n }\n ],\n \"jurisdiction\": \"id\",\n \"licenseType\": \"audiologist\",\n \"privilegeId\": \"\",\n \"providerId\": \"583662f6-05d7-4dd9-af6c-ce0620242a3d\"\n}", "code": 200, "cookie": [], "header": [ @@ -2690,7 +2828,7 @@ "value": "application/json" } ], - "id": "f4fa57e2-59fc-4d08-9064-9c463bdf3623", + "id": "81c3383d-956b-43b4-bde5-000fc794ad59", "name": "200 response", "originalRequest": { "body": {}, @@ -2742,7 +2880,7 @@ "item": [ { "event": [], - "id": "de5fed5f-96c8-4f50-ad5c-db3229e0be7d", + "id": "9077f521-ed60-49ca-8d52-e65d3cc152c1", "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/investigation", "protocolProfileBehavior": { "disableBodyPruning": true @@ -2845,7 +2983,7 @@ "value": "application/json" } ], - "id": "48143152-8047-4218-9dc4-9ffe347769fc", + "id": "3758f6ae-2f15-466d-b2ef-c784232e8b72", "name": "200 response", "originalRequest": { "body": { @@ -2907,7 +3045,7 @@ "item": [ { "event": [], - "id": "ed605afe-0ca3-4ee9-9ec9-6f76a41f2824", + "id": "de970ed9-28b4-4220-8e97-649c8aa27029", "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/investigation/:investigationId", "protocolProfileBehavior": { "disableBodyPruning": true @@ -2921,7 +3059,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 \"encumbranceEffectiveDate\": \"1495-02-30\",\n \"encumbranceType\": \"other monitoring\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"clinicalPrivilegeActionCategory\": \"\"\n }\n}" }, "description": {}, "header": [ @@ -3021,7 +3159,7 @@ "value": "application/json" } ], - "id": "3d3b31c8-15e4-4652-9ced-ff23024a7cd0", + "id": "a802529f-e8ad-4d96-bfc1-b910057c9002", "name": "200 response", "originalRequest": { "body": { @@ -3032,7 +3170,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 \"encumbranceEffectiveDate\": \"1495-02-30\",\n \"encumbranceType\": \"other monitoring\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"clinicalPrivilegeActionCategory\": \"\"\n }\n}" }, "header": [ { @@ -3105,7 +3243,7 @@ "item": [ { "event": [], - "id": "d369e9fc-9a44-4e34-9f10-f4b7d700268b", + "id": "4d6aaf1b-cf4d-4e78-91d0-8aecd3e3552a", "name": "/v1/compacts/:compact/providers/:providerId/ssn", "protocolProfileBehavior": { "disableBodyPruning": true @@ -3161,7 +3299,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"ssn\": \"227-14-4166\"\n}", + "body": "{\n \"ssn\": \"737-45-5308\"\n}", "code": 200, "cookie": [], "header": [ @@ -3170,7 +3308,7 @@ "value": "application/json" } ], - "id": "e4d02317-6458-4453-901b-b355ecea038e", + "id": "958fa077-b454-4bf4-98cc-5f7241593261", "name": "200 response", "originalRequest": { "body": {}, @@ -3223,7 +3361,7 @@ "item": [ { "event": [], - "id": "c631e2a7-bf4c-49da-8ebe-44c63db91758", + "id": "c264e81b-ce45-4ff3-a934-ac9d9bb0e76f", "name": "/v1/compacts/:compact/staff-users", "protocolProfileBehavior": { "disableBodyPruning": true @@ -3267,7 +3405,7 @@ "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}", + "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 \"consectetur8\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"labore_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"commodo__\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"est_8c7\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"active\",\n \"userId\": \"\"\n },\n {\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"irure1c1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"elit_491\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"veniam_d7\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"et6d\": {\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": [ @@ -3285,7 +3423,7 @@ "value": "" } ], - "id": "f82015f7-677a-410b-b0fa-a0b2a610cd86", + "id": "e915276d-04d4-485a-a0fe-081a193b6d9e", "name": "200 response", "originalRequest": { "body": {}, @@ -3324,7 +3462,7 @@ }, { "event": [], - "id": "29d97a42-4046-4876-8a17-84064d3ddd00", + "id": "ff7f9de4-bbff-4650-88a8-683d49d1fa01", "name": "/v1/compacts/:compact/staff-users", "protocolProfileBehavior": { "disableBodyPruning": true @@ -3338,7 +3476,7 @@ "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}" + "raw": "{\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"eiusmod_f1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"irure_7\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"animd\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"reprehenderit_4\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"ut69\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"deserunt5\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"commodo_a_5\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"elitcc\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"consectetur_f\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"ut7_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"consecteturf_9\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"consecteturbe\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"nulla_3b\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"veniamfc\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n }\n}" }, "description": {}, "header": [ @@ -3381,7 +3519,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 \"eiusmod_6cf\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"eu_ec\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"adipisicing3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"quis_a4b\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"Ut_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"commodo_b03\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"consequat_5b\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"inactive\",\n \"userId\": \"\"\n}", "code": 200, "cookie": [], "header": [ @@ -3399,7 +3537,7 @@ "value": "" } ], - "id": "f6195ee8-b64e-4893-a4a6-6d3a6dc4a12f", + "id": "e9523381-d32a-4378-9b03-a5a12493e4de", "name": "200 response", "originalRequest": { "body": { @@ -3410,7 +3548,7 @@ "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}" + "raw": "{\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"eiusmod_f1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"irure_7\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"animd\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"reprehenderit_4\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"ut69\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"deserunt5\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"commodo_a_5\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"elitcc\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"consectetur_f\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"ut7_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"consecteturf_9\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"consecteturbe\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"nulla_3b\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"veniamfc\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n }\n}" }, "header": [ { @@ -3454,7 +3592,7 @@ "item": [ { "event": [], - "id": "b2755dc9-d52a-4cff-a777-90f4b620047d", + "id": "4354e238-aad8-4067-a977-5f6ad155cf8c", "name": "/v1/compacts/:compact/staff-users/:userId", "protocolProfileBehavior": { "disableBodyPruning": true @@ -3509,7 +3647,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 \"eiusmod_6cf\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"eu_ec\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"adipisicing3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"quis_a4b\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"Ut_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"commodo_b03\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"consequat_5b\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"inactive\",\n \"userId\": \"\"\n}", "code": 200, "cookie": [], "header": [ @@ -3527,7 +3665,7 @@ "value": "" } ], - "id": "ff17dff0-146d-45cb-a8da-d8ae414cb769", + "id": "3ae9c8f4-06c7-4eae-a7a7-ddddeecdaaa6", "name": "200 response", "originalRequest": { "body": {}, @@ -3574,7 +3712,7 @@ "value": "application/json" } ], - "id": "33b13941-da68-4139-b292-78d80a65e7dc", + "id": "5684861e-0d48-4833-8a81-f008d5f31316", "name": "404 response", "originalRequest": { "body": {}, @@ -3614,7 +3752,7 @@ }, { "event": [], - "id": "1b4f0101-5b64-45c0-80c4-a1cd49535c02", + "id": "c1ab59ca-f3e7-4f31-b2ef-02e8c914a77d", "name": "/v1/compacts/:compact/staff-users/:userId", "protocolProfileBehavior": { "disableBodyPruning": true @@ -3678,7 +3816,7 @@ "value": "application/json" } ], - "id": "ecd839cc-ba61-4083-95bd-aa680c301748", + "id": "88f96571-7f15-4875-a43b-563a923cbd5f", "name": "200 response", "originalRequest": { "body": {}, @@ -3725,7 +3863,7 @@ "value": "application/json" } ], - "id": "65646a0b-ca6d-4a2d-8126-cd2f25299dc8", + "id": "75415609-04e6-49a9-bb3d-42fec15c254e", "name": "404 response", "originalRequest": { "body": {}, @@ -3765,7 +3903,7 @@ }, { "event": [], - "id": "cbf95d6f-f5b2-4583-8610-e41700a748d6", + "id": "5ec44c02-5907-4fa7-a6c9-2b475784ae55", "name": "/v1/compacts/:compact/staff-users/:userId", "protocolProfileBehavior": { "disableBodyPruning": true @@ -3779,7 +3917,7 @@ "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}" + "raw": "{\n \"permissions\": {\n \"in__\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"mollita_\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n }\n}" }, "description": {}, "header": [ @@ -3833,7 +3971,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 \"eiusmod_6cf\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"eu_ec\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"adipisicing3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"quis_a4b\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"Ut_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"commodo_b03\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"consequat_5b\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"inactive\",\n \"userId\": \"\"\n}", "code": 200, "cookie": [], "header": [ @@ -3851,7 +3989,7 @@ "value": "" } ], - "id": "b9442f93-4e9e-43c8-aa18-4306a45625ea", + "id": "91da90aa-d7e6-4aca-8274-e3b8010564a0", "name": "200 response", "originalRequest": { "body": { @@ -3862,7 +4000,7 @@ "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}" + "raw": "{\n \"permissions\": {\n \"in__\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"mollita_\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n }\n}" }, "header": [ { @@ -3911,7 +4049,7 @@ "value": "application/json" } ], - "id": "858ab0b2-7cba-4745-b11d-d0e2a111cae5", + "id": "d4e2e5cc-7ada-4e4c-8126-f6f4a27a5e9a", "name": "404 response", "originalRequest": { "body": { @@ -3922,7 +4060,7 @@ "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}" + "raw": "{\n \"permissions\": {\n \"in__\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"mollita_\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n }\n}" }, "header": [ { @@ -3967,7 +4105,7 @@ "item": [ { "event": [], - "id": "997abfd5-71da-47e6-acc9-f610c951a189", + "id": "2bc4edca-4c04-4a49-a1ef-cb32ea0ff815", "name": "/v1/compacts/:compact/staff-users/:userId/reinvite", "protocolProfileBehavior": { "disableBodyPruning": true @@ -4032,7 +4170,7 @@ "value": "application/json" } ], - "id": "aa1266af-7d68-42d7-bec9-d0eb27d1e8e9", + "id": "37dcf7e1-99a3-4ed6-8561-a6109227e31f", "name": "200 response", "originalRequest": { "body": {}, @@ -4080,7 +4218,7 @@ "value": "application/json" } ], - "id": "ea46b05f-ab59-4d7a-bfe5-439fc806a761", + "id": "6f17a2fb-b055-4943-9004-4be81e5769a7", "name": "404 response", "originalRequest": { "body": {}, @@ -4145,7 +4283,7 @@ "item": [ { "event": [], - "id": "4297e674-e814-426c-abf4-ca1c66004bde", + "id": "5dac1ee7-d35c-4511-ae08-3592dcee9ea5", "name": "/v1/flags/:flagId/check", "protocolProfileBehavior": { "disableBodyPruning": true @@ -4162,7 +4300,7 @@ "language": "json" } }, - "raw": "{\n \"context\": {\n \"userId\": \"\",\n \"customAttributes\": {\n \"key_0\": \"\",\n \"key_1\": \"\"\n }\n }\n}" + "raw": "{\n \"context\": {\n \"userId\": \"\",\n \"customAttributes\": {\n \"exercitation_\": \"\"\n }\n }\n}" }, "description": {}, "header": [ @@ -4214,7 +4352,7 @@ "value": "application/json" } ], - "id": "f6caa750-5414-4442-a2fa-0ef078eef64a", + "id": "7aa3319c-3567-492c-a462-c70e36ec6858", "name": "200 response", "originalRequest": { "body": { @@ -4225,7 +4363,7 @@ "language": "json" } }, - "raw": "{\n \"context\": {\n \"userId\": \"\",\n \"customAttributes\": {\n \"key_0\": \"\",\n \"key_1\": \"\"\n }\n }\n}" + "raw": "{\n \"context\": {\n \"userId\": \"\",\n \"customAttributes\": {\n \"exercitation_\": \"\"\n }\n }\n}" }, "header": [ { @@ -4283,7 +4421,7 @@ "item": [ { "event": [], - "id": "e21d0d72-bd18-47cc-8b5b-33b4352dc8b7", + "id": "0af77e59-8737-4510-8d25-578e77c5cdd2", "name": "/v1/provider-users/initiateRecovery", "protocolProfileBehavior": { "disableBodyPruning": true @@ -4300,7 +4438,7 @@ "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 \"compact\": \"aslp\",\n \"dob\": \"1907-08-04\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdiction\": \"ak\",\n \"licenseType\": \"audiologist\",\n \"partialSocial\": \"2423\",\n \"password\": \"\",\n \"recaptchaToken\": \"\",\n \"username\": \"\"\n}" }, "description": {}, "header": [ @@ -4340,7 +4478,7 @@ "value": "application/json" } ], - "id": "aab9dc02-e8ff-4f4a-8dcf-8c53a2d3588d", + "id": "c024bdc9-a963-4a9b-9f78-15ab05d8ae84", "name": "200 response", "originalRequest": { "body": { @@ -4351,7 +4489,7 @@ "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 \"compact\": \"aslp\",\n \"dob\": \"1907-08-04\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdiction\": \"ak\",\n \"licenseType\": \"audiologist\",\n \"partialSocial\": \"2423\",\n \"password\": \"\",\n \"recaptchaToken\": \"\",\n \"username\": \"\"\n}" }, "header": [ { @@ -4389,7 +4527,7 @@ "item": [ { "event": [], - "id": "a764b1b7-b990-4ab1-8ed1-dd8e23abb02c", + "id": "5e0a9bd9-8096-4faa-b907-a70aeccb6071", "name": "/v1/provider-users/me", "protocolProfileBehavior": { "disableBodyPruning": true @@ -4421,7 +4559,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\": \"11-17\",\n \"compact\": \"aslp\",\n \"dateOfExpiration\": \"1016-04-15\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"licenseJurisdiction\": \"mt\",\n \"licenses\": [\n {\n \"compact\": \"octp\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"2174-01-13\",\n \"dateOfIssuance\": \"2125-08-07\",\n \"dateOfRenewal\": \"2884-03-30\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"history\": [\n {\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"ok\",\n \"previous\": {\n \"dateOfExpiration\": \"2765-01-27\",\n \"dateOfIssuance\": \"2200-04-07\",\n \"dateOfRenewal\": \"1960-12-31\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"4323861062\",\n \"compactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2605-04-10\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+876228646884801\",\n \"licenseStatus\": \"active\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"licenseDeactivation\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"speech-language pathologist\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"npi\": \"5362356702\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2573-10-02\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"1792-01-14\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"1556-01-31\",\n \"phoneNumber\": \"+04732654\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"1887-10-03\",\n \"licenseStatus\": \"active\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n },\n {\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"in\",\n \"previous\": {\n \"dateOfExpiration\": \"2136-12-01\",\n \"dateOfIssuance\": \"1696-01-31\",\n \"dateOfRenewal\": \"1964-10-31\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"4155100534\",\n \"compactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2824-07-03\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+38146683\",\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\": \"8589103256\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1671-10-13\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2218-12-11\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"2539-10-14\",\n \"phoneNumber\": \"+86007188095\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"1897-01-08\",\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\": \"mn\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"licenseStatus\": \"inactive\",\n \"licenseType\": \"occupational therapy assistant\",\n \"middleName\": \"\",\n \"providerId\": \"cfdbcbbc-6c72-4deb-8651-2fd9915f5573\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"investigations\": [\n {\n \"compact\": \"coun\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"me\",\n \"licenseType\": \"\",\n \"providerId\": \"63b94041-8f0b-43c5-971a-478833b5d6e6\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"coun\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"ny\",\n \"licenseType\": \"\",\n \"providerId\": \"969c9a49-d226-443b-aaa1-dcd93df0300a\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"licenseNumber\": \"\",\n \"investigationStatus\": \"underInvestigation\",\n \"npi\": \"9667390556\",\n \"dateOfBirth\": \"2014-10-28\",\n \"ssnLastFour\": \"5961\",\n \"phoneNumber\": \"+215555457776743\",\n \"licenseStatusName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"octp\",\n \"creationDate\": \"2979-12-30\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2803-07-04\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"me\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"ea82b231-5b5a-41a1-bd61-170eda210d44\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1792-11-04\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"aslp\",\n \"creationDate\": \"2045-12-16\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2301-05-09\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"id\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"8d7a80d6-0190-4b0a-8563-1fb067203716\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1125-04-31\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n }\n ]\n },\n {\n \"compact\": \"octp\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"1160-11-31\",\n \"dateOfIssuance\": \"2576-11-31\",\n \"dateOfRenewal\": \"2046-09-25\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"history\": [\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"md\",\n \"previous\": {\n \"dateOfExpiration\": \"1355-01-03\",\n \"dateOfIssuance\": \"2864-04-16\",\n \"dateOfRenewal\": \"1651-04-11\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"7235513969\",\n \"compactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2108-12-30\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+9730063408781\",\n \"licenseStatus\": \"active\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"expiration\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"speech-language pathologist\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"npi\": \"4979399694\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1169-04-20\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2610-03-30\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"1235-06-10\",\n \"phoneNumber\": \"+0624416018\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"2120-08-29\",\n \"licenseStatus\": \"active\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n },\n {\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"mt\",\n \"previous\": {\n \"dateOfExpiration\": \"2247-12-07\",\n \"dateOfIssuance\": \"1732-04-07\",\n \"dateOfRenewal\": \"2490-01-20\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"4954399462\",\n \"compactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1542-12-01\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+06725848936\",\n \"licenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"emailChange\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"occupational therapy assistant\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"npi\": \"1595699795\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2091-12-09\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2418-05-05\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"2702-10-02\",\n \"phoneNumber\": \"+37788018735632\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"1105-10-31\",\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\": \"ny\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"licensed professional counselor\",\n \"middleName\": \"\",\n \"providerId\": \"313794c2-e6a4-40a2-9e20-1fd7af3d276d\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"investigations\": [\n {\n \"compact\": \"coun\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"ms\",\n \"licenseType\": \"\",\n \"providerId\": \"f47123b6-8f51-44f3-a1cb-7b1a8e15611f\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"aslp\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"nh\",\n \"licenseType\": \"\",\n \"providerId\": \"9594910b-52b8-4b5d-985b-9750832cd1c0\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"licenseNumber\": \"\",\n \"investigationStatus\": \"underInvestigation\",\n \"npi\": \"2736368753\",\n \"dateOfBirth\": \"2164-07-02\",\n \"ssnLastFour\": \"4355\",\n \"phoneNumber\": \"+70372285\",\n \"licenseStatusName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"aslp\",\n \"creationDate\": \"2310-07-04\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2892-12-05\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"ia\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"6cabca4e-7fbe-4e81-9a6c-8e4cfb4bd7df\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2524-12-31\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"octp\",\n \"creationDate\": \"1388-04-19\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2079-09-31\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"wa\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"59d6846b-391f-4b14-80fe-41614be8372d\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1284-01-19\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"militaryAffiliations\": [\n {\n \"affiliationType\": \"militaryMemberSpouse\",\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"\",\n \"dateOfUpload\": \"2656-06-30\",\n \"fileNames\": [\n \"\",\n \"\"\n ],\n \"providerId\": \"33ff6892-8ba6-46f7-987e-f1fddba57f4b\",\n \"status\": \"inactive\",\n \"type\": \"militaryAffiliation\",\n \"downloadLinks\": [\n {\n \"fileName\": \"\",\n \"url\": \"\"\n },\n {\n \"fileName\": \"\",\n \"url\": \"\"\n }\n ]\n },\n {\n \"affiliationType\": \"militaryMember\",\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"\",\n \"dateOfUpload\": \"2442-01-07\",\n \"fileNames\": [\n \"\",\n \"\"\n ],\n \"providerId\": \"959e6e13-9d35-4d46-8091-12d3cb827e45\",\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 \"md\",\n \"nc\"\n ],\n \"privileges\": [\n {\n \"administratorSetStatus\": \"active\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compact\": \"octp\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1102-12-12\",\n \"dateOfIssuance\": \"1313-12-05\",\n \"dateOfRenewal\": \"2701-05-30\",\n \"dateOfUpdate\": \"\",\n \"history\": [\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"in\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"2836-07-27\",\n \"dateOfIssuance\": \"2788-10-30\",\n \"dateOfRenewal\": \"1280-10-31\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"id\",\n \"privilegeId\": \"\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"ok\",\n \"type\": \"privilege\",\n \"providerId\": \"a8431870-2a46-424f-9be6-9d9612adc5bd\",\n \"status\": \"inactive\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"expiration\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"speech-language pathologist\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"ok\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"mn\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"1612-12-31\",\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"2400-02-30\",\n \"privilegeId\": \"\",\n \"providerId\": \"040e7e14-5972-4398-95ee-399a7036cfb7\",\n \"dateOfRenewal\": \"2756-10-09\",\n \"dateOfUpdate\": \"\",\n \"status\": \"inactive\"\n }\n },\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"fl\",\n \"previous\": {\n \"administratorSetStatus\": \"active\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"2891-04-07\",\n \"dateOfIssuance\": \"1522-12-30\",\n \"dateOfRenewal\": \"1235-10-15\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"oh\",\n \"privilegeId\": \"\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"ny\",\n \"type\": \"privilege\",\n \"providerId\": \"8174175e-978b-46f6-b0d5-75dc883e83ea\",\n \"status\": \"active\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"deactivation\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"speech-language pathologist\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"tn\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"ut\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"1015-11-16\",\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"2808-10-30\",\n \"privilegeId\": \"\",\n \"providerId\": \"402bc3a2-4a76-49df-a95e-83cdc8d4c6c9\",\n \"dateOfRenewal\": \"2912-11-05\",\n \"dateOfUpdate\": \"\",\n \"status\": \"inactive\"\n }\n }\n ],\n \"jurisdiction\": \"il\",\n \"licenseJurisdiction\": \"ok\",\n \"licenseType\": \"licensed professional counselor\",\n \"privilegeId\": \"\",\n \"providerId\": \"fabef33a-bb76-469e-9794-0fb12dabb9d2\",\n \"status\": \"inactive\",\n \"type\": \"privilege\",\n \"investigationStatus\": \"underInvestigation\",\n \"investigations\": [\n {\n \"compact\": \"coun\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"nc\",\n \"licenseType\": \"\",\n \"providerId\": \"b8939464-4b19-4a6d-bc77-5bfd9d0d7ea1\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"aslp\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"az\",\n \"licenseType\": \"\",\n \"providerId\": \"aef09a8b-757f-4a4d-9739-12dfaac27da4\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"1589-10-10\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1165-04-31\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"ca\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"deacc877-04f8-425a-85af-261178c514ae\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1861-12-25\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"2914-08-03\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2120-04-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"id\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"cf4315a9-ccdb-42a8-854b-7e90def7885a\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1914-04-30\",\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\": \"octp\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1723-06-11\",\n \"dateOfIssuance\": \"1203-12-15\",\n \"dateOfRenewal\": \"2871-11-10\",\n \"dateOfUpdate\": \"\",\n \"history\": [\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"nj\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1058-12-11\",\n \"dateOfIssuance\": \"2149-11-30\",\n \"dateOfRenewal\": \"1018-08-01\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"fl\",\n \"privilegeId\": \"\",\n \"compact\": \"coun\",\n \"jurisdiction\": \"mi\",\n \"type\": \"privilege\",\n \"providerId\": \"5e722037-8d03-47ab-afaa-d915b0cac757\",\n \"status\": \"inactive\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"lifting_encumbrance\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"occupational therapy assistant\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"tn\",\n \"compact\": \"coun\",\n \"jurisdiction\": \"wi\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"1426-03-06\",\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"1199-10-06\",\n \"privilegeId\": \"\",\n \"providerId\": \"283c9149-73e8-40a4-9c6a-788e01bbed79\",\n \"dateOfRenewal\": \"1271-04-30\",\n \"dateOfUpdate\": \"\",\n \"status\": \"inactive\"\n }\n },\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"id\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"2341-12-30\",\n \"dateOfIssuance\": \"1576-03-30\",\n \"dateOfRenewal\": \"1709-03-03\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"wy\",\n \"privilegeId\": \"\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"ut\",\n \"type\": \"privilege\",\n \"providerId\": \"366e661a-421d-4689-b899-793f7233747c\",\n \"status\": \"inactive\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"other\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"occupational therapist\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"hi\",\n \"compact\": \"coun\",\n \"jurisdiction\": \"ky\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"2939-01-02\",\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"1022-10-24\",\n \"privilegeId\": \"\",\n \"providerId\": \"fbbe0975-c404-46f0-9dc7-399ffeae3683\",\n \"dateOfRenewal\": \"1236-01-31\",\n \"dateOfUpdate\": \"\",\n \"status\": \"active\"\n }\n }\n ],\n \"jurisdiction\": \"ms\",\n \"licenseJurisdiction\": \"mn\",\n \"licenseType\": \"licensed professional counselor\",\n \"privilegeId\": \"\",\n \"providerId\": \"8d94428c-bb5b-4140-8aa6-066bffffdfdc\",\n \"status\": \"inactive\",\n \"type\": \"privilege\",\n \"investigationStatus\": \"underInvestigation\",\n \"investigations\": [\n {\n \"compact\": \"coun\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"md\",\n \"licenseType\": \"\",\n \"providerId\": \"6b200887-a4e3-4124-acdc-fffce69807f6\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"octp\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"ak\",\n \"licenseType\": \"\",\n \"providerId\": \"3eb7845c-a341-4b11-8db7-6186a3e0f7e9\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"aslp\",\n \"creationDate\": \"2881-04-31\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1128-04-14\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"in\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"c38f6c41-d865-4c37-ae18-63ce5fd18c55\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2871-05-08\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"2484-04-31\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2979-11-04\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"fl\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"be457ff9-9a51-404b-9d23-b9f5eaa9d6c6\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2146-10-05\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"providerId\": \"098edacb-8ab3-4f0b-a655-c9f26be8827c\",\n \"type\": \"provider\",\n \"npi\": \"5990059533\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1157-07-30\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"suffix\": \"\",\n \"currentHomeJurisdiction\": \"wi\",\n \"ssnLastFour\": \"7344\",\n \"licenseStatus\": \"active\",\n \"middleName\": \"\",\n \"compactConnectRegisteredEmailAddress\": \"\"\n}", "code": 200, "cookie": [], "header": [ @@ -4430,7 +4568,7 @@ "value": "application/json" } ], - "id": "2e29babb-949f-4960-b5cd-e641b2923e21", + "id": "c5a41dd5-e566-490c-8587-7f55365c2cee", "name": "200 response", "originalRequest": { "body": {}, @@ -4471,7 +4609,7 @@ "item": [ { "event": [], - "id": "154faaa4-1c24-44e2-869e-f2862cca561c", + "id": "7054f10c-2607-45ea-8edb-9ad3aff21dbf", "name": "/v1/provider-users/me/email", "protocolProfileBehavior": { "disableBodyPruning": true @@ -4526,7 +4664,7 @@ "value": "application/json" } ], - "id": "334aee2c-a6ef-48a3-acf9-695813214900", + "id": "e4d9943a-510c-4211-a9a0-6a6c5dde32c3", "name": "200 response", "originalRequest": { "body": { @@ -4581,7 +4719,7 @@ "item": [ { "event": [], - "id": "1a449848-b642-4d2b-91f3-bce7b7f8f0f8", + "id": "3f398de5-c110-45ba-a446-893b7ffb5213", "name": "/v1/provider-users/me/email/verify", "protocolProfileBehavior": { "disableBodyPruning": true @@ -4595,7 +4733,7 @@ "language": "json" } }, - "raw": "{\n \"verificationCode\": \"6671\"\n}" + "raw": "{\n \"verificationCode\": \"3402\"\n}" }, "description": {}, "header": [ @@ -4637,7 +4775,7 @@ "value": "application/json" } ], - "id": "63d859da-d627-49e6-84b5-0f85687795b4", + "id": "cae72537-da94-4aab-b907-31cedea0288d", "name": "200 response", "originalRequest": { "body": { @@ -4648,7 +4786,7 @@ "language": "json" } }, - "raw": "{\n \"verificationCode\": \"6671\"\n}" + "raw": "{\n \"verificationCode\": \"3402\"\n}" }, "header": [ { @@ -4699,7 +4837,7 @@ "item": [ { "event": [], - "id": "0e3cf433-7bf5-4222-a18c-e7370b5b0af6", + "id": "e9d1a9bb-2372-4c55-8a61-cae3b4745fdf", "name": "/v1/provider-users/me/home-jurisdiction", "protocolProfileBehavior": { "disableBodyPruning": true @@ -4713,7 +4851,7 @@ "language": "json" } }, - "raw": "{\n \"jurisdiction\": \"ia\"\n}" + "raw": "{\n \"jurisdiction\": \"de\"\n}" }, "description": {}, "header": [ @@ -4754,7 +4892,7 @@ "value": "application/json" } ], - "id": "b3a68e61-c492-42b9-a62d-d2c3ffe4caac", + "id": "476299f3-96e3-4920-8e1a-3012b12f8eeb", "name": "200 response", "originalRequest": { "body": { @@ -4765,7 +4903,7 @@ "language": "json" } }, - "raw": "{\n \"jurisdiction\": \"ia\"\n}" + "raw": "{\n \"jurisdiction\": \"de\"\n}" }, "header": [ { @@ -4824,7 +4962,7 @@ "item": [ { "event": [], - "id": "a5573123-c770-472d-aea9-e0fdc266d466", + "id": "b30f32c2-4076-4abc-a419-ddd5048c132e", "name": "/v1/provider-users/me/jurisdiction/:jurisdiction/licenseType/:licenseType/history", "protocolProfileBehavior": { "disableBodyPruning": true @@ -4882,7 +5020,7 @@ "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}", + "body": "{\n \"compact\": \"octp\",\n \"events\": [\n {\n \"createDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"effectiveDate\": \"1680-11-27\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"encumbrance\",\n \"note\": \"\"\n },\n {\n \"createDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"effectiveDate\": \"2994-12-28\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"other\",\n \"note\": \"\"\n }\n ],\n \"jurisdiction\": \"id\",\n \"licenseType\": \"audiologist\",\n \"privilegeId\": \"\",\n \"providerId\": \"583662f6-05d7-4dd9-af6c-ce0620242a3d\"\n}", "code": 200, "cookie": [], "header": [ @@ -4891,7 +5029,7 @@ "value": "application/json" } ], - "id": "f187e63c-a81d-4da7-88fb-af6a9b178320", + "id": "4af8fdd8-38da-45ca-a6db-fbd25111a3a6", "name": "200 response", "originalRequest": { "body": {}, @@ -4952,7 +5090,7 @@ "item": [ { "event": [], - "id": "257b92af-e83f-4677-9b8f-8bf35a995060", + "id": "435a6200-6b3d-4a7f-81a9-8871f8a25a62", "name": "/v1/provider-users/me/military-affiliation", "protocolProfileBehavior": { "disableBodyPruning": true @@ -4966,7 +5104,7 @@ "language": "json" } }, - "raw": "{\n \"affiliationType\": \"militaryMemberSpouse\",\n \"fileNames\": [\n \"\",\n \"\"\n ]\n}" + "raw": "{\n \"affiliationType\": \"militaryMember\",\n \"fileNames\": [\n \"\",\n \"\"\n ]\n}" }, "description": {}, "header": [ @@ -4998,7 +5136,7 @@ "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}", + "body": "{\n \"affiliationType\": \"militaryMemberSpouse\",\n \"dateOfUpdate\": \"\",\n \"dateOfUpload\": \"2016-11-12\",\n \"documentUploadFields\": [\n {\n \"fields\": {\n \"mollit8fd\": \"\",\n \"dolore_050\": \"\",\n \"velite9\": \"\"\n },\n \"url\": \"\"\n },\n {\n \"fields\": {\n \"minim7f\": \"\"\n },\n \"url\": \"\"\n }\n ],\n \"status\": \"\",\n \"fileNames\": [\n \"\",\n \"\"\n ]\n}", "code": 200, "cookie": [], "header": [ @@ -5007,7 +5145,7 @@ "value": "application/json" } ], - "id": "dd95b21d-fb30-4eb7-8368-9074c44dff0b", + "id": "5ff97bfb-9590-4ab4-b3f2-805b9ec618af", "name": "200 response", "originalRequest": { "body": { @@ -5018,7 +5156,7 @@ "language": "json" } }, - "raw": "{\n \"affiliationType\": \"militaryMemberSpouse\",\n \"fileNames\": [\n \"\",\n \"\"\n ]\n}" + "raw": "{\n \"affiliationType\": \"militaryMember\",\n \"fileNames\": [\n \"\",\n \"\"\n ]\n}" }, "header": [ { @@ -5059,7 +5197,7 @@ }, { "event": [], - "id": "e6755b3d-9e1c-4153-872d-fd3529b1237e", + "id": "2fcef1ba-b0c7-4199-8438-acebde5430dc", "name": "/v1/provider-users/me/military-affiliation", "protocolProfileBehavior": { "disableBodyPruning": true @@ -5114,7 +5252,7 @@ "value": "application/json" } ], - "id": "00c89a32-e0f2-4a3f-97f2-9539bc3e468c", + "id": "7dd31df9-e908-4ccc-8b97-52a6a7f59218", "name": "200 response", "originalRequest": { "body": { @@ -5175,7 +5313,7 @@ "item": [ { "event": [], - "id": "7a395ce2-3d57-4e53-8f28-bbafb685d43e", + "id": "0339e3c8-8d3d-41e5-b212-2d277f2b1b5b", "name": "/v1/provider-users/registration", "protocolProfileBehavior": { "disableBodyPruning": true @@ -5192,7 +5330,7 @@ "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}" + "raw": "{\n \"compact\": \"\",\n \"dob\": \"2324-09-04\",\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdiction\": \"sc\",\n \"licenseType\": \"speech-language pathologist\",\n \"partialSocial\": \"\",\n \"token\": \"\"\n}" }, "description": {}, "header": [ @@ -5232,7 +5370,7 @@ "value": "application/json" } ], - "id": "148da7ca-0bc4-4cde-9d70-d6ebf187f8f2", + "id": "c540498f-d862-4271-aedc-51dae5bb5c3e", "name": "200 response", "originalRequest": { "body": { @@ -5243,7 +5381,7 @@ "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}" + "raw": "{\n \"compact\": \"\",\n \"dob\": \"2324-09-04\",\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdiction\": \"sc\",\n \"licenseType\": \"speech-language pathologist\",\n \"partialSocial\": \"\",\n \"token\": \"\"\n}" }, "header": [ { @@ -5281,7 +5419,7 @@ "item": [ { "event": [], - "id": "1ae7f606-a7b2-40f3-a0ac-cfe256c74a18", + "id": "55cd4d84-a628-4add-be1c-ae554d87ecfc", "name": "/v1/provider-users/verifyRecovery", "protocolProfileBehavior": { "disableBodyPruning": true @@ -5298,7 +5436,7 @@ "language": "json" } }, - "raw": "{\n \"compact\": \"octp\",\n \"providerId\": \"4ea47a54-7e2f-4daf-972c-3ccb4e0769d5\",\n \"recaptchaToken\": \"\",\n \"recoveryToken\": \"\"\n}" + "raw": "{\n \"compact\": \"octp\",\n \"providerId\": \"54c57e85-92d7-4c87-984c-5c4614cdcef2\",\n \"recaptchaToken\": \"\",\n \"recoveryToken\": \"\"\n}" }, "description": {}, "header": [ @@ -5338,7 +5476,7 @@ "value": "application/json" } ], - "id": "19213d8a-2ffe-4638-9219-38ed3bf87dd8", + "id": "e9e3c5de-c55e-452f-9e9b-9f720a70d871", "name": "200 response", "originalRequest": { "body": { @@ -5349,7 +5487,7 @@ "language": "json" } }, - "raw": "{\n \"compact\": \"octp\",\n \"providerId\": \"4ea47a54-7e2f-4daf-972c-3ccb4e0769d5\",\n \"recaptchaToken\": \"\",\n \"recoveryToken\": \"\"\n}" + "raw": "{\n \"compact\": \"octp\",\n \"providerId\": \"54c57e85-92d7-4c87-984c-5c4614cdcef2\",\n \"recaptchaToken\": \"\",\n \"recoveryToken\": \"\"\n}" }, "header": [ { @@ -5399,7 +5537,7 @@ "item": [ { "event": [], - "id": "a59f2d74-1c6e-4a97-8738-97cb1be17b72", + "id": "3755cff7-19a3-456d-97df-afaec1090895", "name": "/v1/public/compacts/:compact/jurisdictions", "protocolProfileBehavior": { "disableBodyPruning": true @@ -5456,7 +5594,7 @@ "value": "application/json" } ], - "id": "72e88f34-ee26-44ac-a566-c164190a4b7e", + "id": "34f9f419-3299-4095-a56b-c01e82ecf332", "name": "200 response", "originalRequest": { "body": {}, @@ -5497,7 +5635,7 @@ "item": [ { "event": [], - "id": "41c7aac2-b45e-459d-a159-1c6fd4ae71b5", + "id": "40c8362f-e3cf-4e28-b026-a4a9a9b5bfd9", "name": "/v1/public/compacts/:compact/providers/query", "protocolProfileBehavior": { "disableBodyPruning": true @@ -5514,7 +5652,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\": \"8ab484ca-2bcd-4109-8bd1-732bba4947b7\",\n \"jurisdiction\": \"in\",\n \"givenName\": \"\",\n \"familyName\": \"\"\n },\n \"pagination\": {\n \"lastKey\": \"\",\n \"pageSize\": \"\"\n },\n \"sorting\": {\n \"key\": \"dateOfUpdate\",\n \"direction\": \"ascending\"\n }\n}" }, "description": {}, "header": [ @@ -5559,7 +5697,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\": \"coun\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"licenseJurisdiction\": \"fl\",\n \"privilegeJurisdictions\": [\n \"nv\",\n \"sd\"\n ],\n \"providerId\": \"375337bb-657c-4d68-a3a4-0a17dbe4da57\",\n \"type\": \"provider\",\n \"npi\": \"0813673874\",\n \"middleName\": \"\",\n \"suffix\": \"\",\n \"currentHomeJurisdiction\": \"nc\",\n \"dateOfUpdate\": \"\"\n },\n {\n \"compact\": \"coun\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"licenseJurisdiction\": \"nh\",\n \"privilegeJurisdictions\": [\n \"hi\",\n \"az\"\n ],\n \"providerId\": \"f1a40ad2-7d55-476f-a8c9-7e5ea649bee2\",\n \"type\": \"provider\",\n \"npi\": \"9067266875\",\n \"middleName\": \"\",\n \"suffix\": \"\",\n \"currentHomeJurisdiction\": \"mi\",\n \"dateOfUpdate\": \"\"\n }\n ],\n \"query\": {\n \"providerId\": \"d7151651-7040-4ec1-b5ab-b50c7e9e3d21\",\n \"jurisdiction\": \"de\",\n \"givenName\": \"\",\n \"familyName\": \"\"\n },\n \"sorting\": {\n \"key\": \"dateOfUpdate\",\n \"direction\": \"ascending\"\n }\n}", "code": 200, "cookie": [], "header": [ @@ -5568,7 +5706,7 @@ "value": "application/json" } ], - "id": "8997fc38-2c70-4bc4-a3bc-ebe512fd7df8", + "id": "1d724c4c-eb68-4cb2-afaf-03694180f3b8", "name": "200 response", "originalRequest": { "body": { @@ -5579,7 +5717,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\": \"8ab484ca-2bcd-4109-8bd1-732bba4947b7\",\n \"jurisdiction\": \"in\",\n \"givenName\": \"\",\n \"familyName\": \"\"\n },\n \"pagination\": {\n \"lastKey\": \"\",\n \"pageSize\": \"\"\n },\n \"sorting\": {\n \"key\": \"dateOfUpdate\",\n \"direction\": \"ascending\"\n }\n}" }, "header": [ { @@ -5620,7 +5758,7 @@ "item": [ { "event": [], - "id": "591e37e5-f41b-43f7-ab3b-275447a86d24", + "id": "cbc6fd87-3d5b-4f23-839c-fc7ca7e468ea", "name": "/v1/public/compacts/:compact/providers/:providerId", "protocolProfileBehavior": { "disableBodyPruning": true @@ -5679,7 +5817,7 @@ "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}", + "body": "{\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"licenseJurisdiction\": \"ar\",\n \"privilegeJurisdictions\": [\n \"ks\",\n \"tn\"\n ],\n \"providerId\": \"3bf898e8-3033-47b5-a5ac-9a281fc29a1b\",\n \"type\": \"provider\",\n \"privileges\": [\n {\n \"administratorSetStatus\": \"active\",\n \"compact\": \"octp\",\n \"dateOfExpiration\": \"2042-12-01\",\n \"dateOfIssuance\": \"2348-03-16\",\n \"dateOfRenewal\": \"1722-03-01\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"md\",\n \"licenseJurisdiction\": \"ne\",\n \"licenseType\": \"audiologist\",\n \"privilegeId\": \"\",\n \"providerId\": \"f72c3377-a212-4e09-b315-81c4a44eb586\",\n \"status\": \"inactive\",\n \"type\": \"privilege\",\n \"history\": [\n {\n \"compact\": \"aslp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"al\",\n \"licenseType\": \"occupational therapist\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"2831-12-05\",\n \"dateOfIssuance\": \"2694-06-29\",\n \"dateOfRenewal\": \"2944-06-01\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"or\",\n \"privilegeId\": \"\"\n },\n \"providerId\": \"1a8d0b13-f6a8-4136-a24b-9660790964e8\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"licenseDeactivation\",\n \"updatedValues\": {\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"1300-01-05\",\n \"licenseJurisdiction\": \"co\",\n \"privilegeId\": \"\",\n \"dateOfRenewal\": \"1490-03-24\",\n \"dateOfIssuance\": \"1881-12-18\",\n \"dateOfUpdate\": \"\"\n }\n },\n {\n \"compact\": \"aslp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"wv\",\n \"licenseType\": \"licensed professional counselor\",\n \"previous\": {\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"2520-06-30\",\n \"dateOfIssuance\": \"1400-03-31\",\n \"dateOfRenewal\": \"1865-03-24\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"ny\",\n \"privilegeId\": \"\"\n },\n \"providerId\": \"37723210-3255-4a1d-a918-85450c0f525a\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"homeJurisdictionChange\",\n \"updatedValues\": {\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"2338-11-08\",\n \"licenseJurisdiction\": \"dc\",\n \"privilegeId\": \"\",\n \"dateOfRenewal\": \"2388-11-30\",\n \"dateOfIssuance\": \"1039-09-07\",\n \"dateOfUpdate\": \"\"\n }\n }\n ],\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"aslp\",\n \"creationDate\": \"2872-11-07\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1671-06-08\",\n \"jurisdiction\": \"dc\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"4f2fe7b9-5930-4d10-a6e9-1a10a13a101b\",\n \"type\": \"adverseAction\",\n \"effectiveLiftDate\": \"1930-03-28\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"aslp\",\n \"creationDate\": \"2667-10-30\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1936-05-26\",\n \"jurisdiction\": \"al\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"6cd7dfff-a2f5-4976-a393-b74289c629fb\",\n \"type\": \"adverseAction\",\n \"effectiveLiftDate\": \"1074-04-30\"\n }\n ]\n },\n {\n \"administratorSetStatus\": \"active\",\n \"compact\": \"aslp\",\n \"dateOfExpiration\": \"2746-11-31\",\n \"dateOfIssuance\": \"1256-05-31\",\n \"dateOfRenewal\": \"2932-03-30\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"wy\",\n \"licenseJurisdiction\": \"vt\",\n \"licenseType\": \"licensed professional counselor\",\n \"privilegeId\": \"\",\n \"providerId\": \"298f4cbf-8a94-4611-9577-73d80553f030\",\n \"status\": \"inactive\",\n \"type\": \"privilege\",\n \"history\": [\n {\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"co\",\n \"licenseType\": \"licensed professional counselor\",\n \"previous\": {\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"1383-10-18\",\n \"dateOfIssuance\": \"1065-11-30\",\n \"dateOfRenewal\": \"2957-11-17\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"vt\",\n \"privilegeId\": \"\"\n },\n \"providerId\": \"20fb9e08-95e4-4362-8c92-5a18923429de\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"registration\",\n \"updatedValues\": {\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"2259-10-09\",\n \"licenseJurisdiction\": \"vt\",\n \"privilegeId\": \"\",\n \"dateOfRenewal\": \"1144-10-30\",\n \"dateOfIssuance\": \"2121-10-03\",\n \"dateOfUpdate\": \"\"\n }\n },\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"pr\",\n \"licenseType\": \"audiologist\",\n \"previous\": {\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"2458-12-23\",\n \"dateOfIssuance\": \"1261-11-14\",\n \"dateOfRenewal\": \"2780-02-08\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"ri\",\n \"privilegeId\": \"\"\n },\n \"providerId\": \"502c873b-a49c-4b13-aad0-a3ffca8499e5\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"renewal\",\n \"updatedValues\": {\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"1230-01-30\",\n \"licenseJurisdiction\": \"nh\",\n \"privilegeId\": \"\",\n \"dateOfRenewal\": \"2726-08-08\",\n \"dateOfIssuance\": \"1947-02-28\",\n \"dateOfUpdate\": \"\"\n }\n }\n ],\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"1147-12-24\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2118-06-03\",\n \"jurisdiction\": \"la\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"4e47be52-dd03-4cf3-a285-6f5a86ba8a6b\",\n \"type\": \"adverseAction\",\n \"effectiveLiftDate\": \"1395-02-24\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"1033-10-08\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2644-08-01\",\n \"jurisdiction\": \"ks\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"d0a3dff7-089a-40b5-a395-18e297112b9c\",\n \"type\": \"adverseAction\",\n \"effectiveLiftDate\": \"1543-04-20\"\n }\n ]\n }\n ],\n \"npi\": \"7554928476\",\n \"suffix\": \"\",\n \"currentHomeJurisdiction\": \"pa\",\n \"middleName\": \"\"\n}", "code": 200, "cookie": [], "header": [ @@ -5688,7 +5826,7 @@ "value": "application/json" } ], - "id": "78220f24-ba91-48b4-bca6-26cc84183cd3", + "id": "e5ecbe28-b28c-4101-88b1-ed125434421d", "name": "200 response", "originalRequest": { "body": {}, @@ -5736,7 +5874,7 @@ "item": [ { "event": [], - "id": "fd716311-dce7-4a37-979f-304a141f0b43", + "id": "032bf11b-1f95-4282-b74d-b38ab478b7b1", "name": "/v1/public/compacts/:compact/providers/:providerId/jurisdiction/:jurisdiction/licenseType/:licenseType/history", "protocolProfileBehavior": { "disableBodyPruning": true @@ -5820,7 +5958,7 @@ "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}", + "body": "{\n \"compact\": \"octp\",\n \"events\": [\n {\n \"createDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"effectiveDate\": \"1680-11-27\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"encumbrance\",\n \"note\": \"\"\n },\n {\n \"createDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"effectiveDate\": \"2994-12-28\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"other\",\n \"note\": \"\"\n }\n ],\n \"jurisdiction\": \"id\",\n \"licenseType\": \"audiologist\",\n \"privilegeId\": \"\",\n \"providerId\": \"583662f6-05d7-4dd9-af6c-ce0620242a3d\"\n}", "code": 200, "cookie": [], "header": [ @@ -5829,7 +5967,7 @@ "value": "application/json" } ], - "id": "231c170a-79c9-4426-9ab8-9a76fb6dbd78", + "id": "15daa21a-2465-4664-9b37-c2a2ee30a065", "name": "200 response", "originalRequest": { "body": {}, @@ -5900,7 +6038,7 @@ "item": [ { "event": [], - "id": "52dc84cd-5caa-447f-a9c6-0f67edb88947", + "id": "9132cd97-d1c9-436b-a96f-93add3043b0b", "name": "/v1/public/jurisdictions/live", "protocolProfileBehavior": { "disableBodyPruning": true @@ -5946,7 +6084,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"key_0\": [\n \"ut\",\n \"wv\"\n ]\n}", + "body": "{\n \"ad_9\": [\n \"wy\",\n \"mi\"\n ]\n}", "code": 200, "cookie": [], "header": [ @@ -5955,7 +6093,7 @@ "value": "application/json" } ], - "id": "c7228003-70c9-4263-8f12-b132ff502951", + "id": "9227d97e-c5ff-40a3-b8ee-2f8ea5f15360", "name": "200 response", "originalRequest": { "body": {}, @@ -6011,7 +6149,7 @@ "item": [ { "event": [], - "id": "77058b43-efa8-4c30-bba6-f9d1edf387db", + "id": "7ba27680-1914-4b91-888c-2f1b960d4065", "name": "/v1/purchases/privileges", "protocolProfileBehavior": { "disableBodyPruning": true @@ -6025,7 +6163,7 @@ "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}" + "raw": "{\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"974\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"217\"\n }\n ],\n \"licenseType\": \"licensed professional counselor\",\n \"orderInformation\": {\n \"opaqueData\": {\n \"dataDescriptor\": \"\",\n \"dataValue\": \"\"\n }\n },\n \"selectedJurisdictions\": [\n \"mn\",\n \"dc\"\n ]\n}" }, "description": {}, "header": [ @@ -6065,7 +6203,7 @@ "value": "application/json" } ], - "id": "6d02f8ec-d2a0-43e6-a831-016fadbf70fc", + "id": "30af5f95-06b7-4957-8dee-3f70c8d62e50", "name": "200 response", "originalRequest": { "body": { @@ -6076,7 +6214,7 @@ "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}" + "raw": "{\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"974\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"217\"\n }\n ],\n \"licenseType\": \"licensed professional counselor\",\n \"orderInformation\": {\n \"opaqueData\": {\n \"dataDescriptor\": \"\",\n \"dataValue\": \"\"\n }\n },\n \"selectedJurisdictions\": [\n \"mn\",\n \"dc\"\n ]\n}" }, "header": [ { @@ -6119,7 +6257,7 @@ "item": [ { "event": [], - "id": "44420427-e1a1-4f6a-8c55-c446bd397150", + "id": "4d2c35fa-d116-4e82-b750-764b0a724e52", "name": "/v1/purchases/privileges/options", "protocolProfileBehavior": { "disableBodyPruning": true @@ -6161,7 +6299,7 @@ "value": "application/json" } ], - "id": "0211181c-f18a-4ee5-974a-3ffd066af744", + "id": "2a975fc5-9ac9-4335-aa71-8e040242b2fa", "name": "200 response", "originalRequest": { "body": {}, @@ -6215,7 +6353,7 @@ "item": [ { "event": [], - "id": "eaef01d3-0498-4681-8a8b-8f61c1fa42bb", + "id": "35c026a5-cd78-43ad-b080-7da7b9adea31", "name": "/v1/staff-users/me", "protocolProfileBehavior": { "disableBodyPruning": true @@ -6247,7 +6385,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 \"eiusmod_6cf\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"eu_ec\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"adipisicing3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"quis_a4b\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"Ut_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"commodo_b03\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"consequat_5b\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"inactive\",\n \"userId\": \"\"\n}", "code": 200, "cookie": [], "header": [ @@ -6265,7 +6403,7 @@ "value": "" } ], - "id": "50509350-3bf2-4d40-9a12-ca779d159cd6", + "id": "25763968-6e6e-4c41-8e8a-7c1ac49850b7", "name": "200 response", "originalRequest": { "body": {}, @@ -6310,7 +6448,7 @@ "value": "application/json" } ], - "id": "3d2c2a65-b4d5-4c77-aadc-794c9e50f644", + "id": "c5a59cc0-89eb-40df-aacf-6dbd5383985c", "name": "404 response", "originalRequest": { "body": {}, @@ -6348,7 +6486,7 @@ }, { "event": [], - "id": "0c887909-602d-46f4-9d1c-b55d4c08c53e", + "id": "4023dbbc-063c-4ff6-ba74-0b6fe929bf6d", "name": "/v1/staff-users/me", "protocolProfileBehavior": { "disableBodyPruning": true @@ -6393,7 +6531,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 \"eiusmod_6cf\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"eu_ec\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"adipisicing3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"quis_a4b\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"Ut_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"commodo_b03\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"consequat_5b\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"inactive\",\n \"userId\": \"\"\n}", "code": 200, "cookie": [], "header": [ @@ -6411,7 +6549,7 @@ "value": "" } ], - "id": "fec1dfd7-6449-47c0-a6b6-d0757a6f4f48", + "id": "a1595309-89a9-44ac-9232-e7b1564c59d7", "name": "200 response", "originalRequest": { "body": { @@ -6469,7 +6607,7 @@ "value": "application/json" } ], - "id": "f6663f03-5203-4ec2-a071-fd93d1b627aa", + "id": "d96b83a8-7383-4004-8e75-5f58f778e796", "name": "404 response", "originalRequest": { "body": { From e950f0ef06bba4926c06c5988aec0849553d06f9 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Mon, 29 Dec 2025 15:54:34 -0700 Subject: [PATCH 27/53] PR feedback --- .../lambdas/python/common/cc_common/data_model/data_client.py | 2 +- .../tests/smoke/military_affiliation_smoke_tests.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py index e439e450a..fafc88ec4 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py @@ -854,7 +854,7 @@ def end_military_affiliation(self, compact: str, provider_id: str) -> None: # Get all military affiliation records that are INITIALIZING or ACTIVE active_military_affiliation_records = provider_user_records.get_military_affiliation_records( filter_condition=lambda record: record.status - in [MilitaryAffiliationStatus.INITIALIZING.value, MilitaryAffiliationStatus.ACTIVE.value] + in [MilitaryAffiliationStatus.INITIALIZING, MilitaryAffiliationStatus.ACTIVE] ) # Create provider update record to track the removal of military status fields diff --git a/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py b/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py index 8d1038496..1c42688e3 100644 --- a/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py +++ b/backend/compact-connect/tests/smoke/military_affiliation_smoke_tests.py @@ -260,7 +260,7 @@ def test_military_affiliation_audit(): f'Military status is not approved. Status: {provider_data_after_approval.get("militaryStatus")}' ) - # Verify militaryStatusNote equals what was passed into the request body + # Verify militaryStatusNote is an empty string if provider_data_after_approval.get('militaryStatusNote') != '': raise SmokeTestFailureException( f'Military status note does not match. Expected empty string, ' From f210f4abe0ee400c45019bc5c7721f00bec85800 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Mon, 29 Dec 2025 16:06:10 -0700 Subject: [PATCH 28/53] PR feedback - remove unneeded .value --- .../python/common/cc_common/data_model/data_client.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py index fafc88ec4..dc880023b 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py @@ -708,7 +708,7 @@ def complete_military_affiliation_initialization(self, compact: str, provider_id 'previous': provider_record.to_dict(), 'createDate': now, 'updatedValues': { - 'militaryStatus': MilitaryAuditStatus.TENTATIVE.value, + 'militaryStatus': MilitaryAuditStatus.TENTATIVE, 'militaryStatusNote': '', }, } @@ -730,7 +730,7 @@ def complete_military_affiliation_initialization(self, compact: str, provider_id 'dateOfUpdate = :dateOfUpdate' ), 'ExpressionAttributeValues': { - ':militaryStatus': {'S': MilitaryAuditStatus.TENTATIVE.value}, + ':militaryStatus': {'S': MilitaryAuditStatus.TENTATIVE}, ':militaryStatusNote': {'S': ''}, ':dateOfUpdate': {'S': self.config.current_standard_datetime.isoformat()}, }, @@ -1006,7 +1006,7 @@ def process_military_audit( 'previous': previous_provider_state, 'createDate': now, 'updatedValues': { - 'militaryStatus': military_status.value, + 'militaryStatus': military_status, 'militaryStatusNote': note_value, }, } @@ -1030,7 +1030,7 @@ def process_military_audit( 'dateOfUpdate = :dateOfUpdate' ), 'ExpressionAttributeValues': { - ':militaryStatus': {'S': military_status.value}, + ':militaryStatus': {'S': military_status}, ':militaryStatusNote': {'S': note_value}, ':dateOfUpdate': {'S': self.config.current_standard_datetime.isoformat()}, }, From 456965d0666a5b597b2e16a82f73530f656cce32 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Tue, 30 Dec 2025 09:53:00 -0700 Subject: [PATCH 29/53] update smoke tests to account for now military status --- .../smoke/purchasing_privileges_smoke_tests.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/backend/compact-connect/tests/smoke/purchasing_privileges_smoke_tests.py b/backend/compact-connect/tests/smoke/purchasing_privileges_smoke_tests.py index 5a65aa5b0..ed3d2058c 100644 --- a/backend/compact-connect/tests/smoke/purchasing_privileges_smoke_tests.py +++ b/backend/compact-connect/tests/smoke/purchasing_privileges_smoke_tests.py @@ -154,11 +154,19 @@ def test_purchasing_privilege(delete_current_privilege: bool = True): 'provision-of-true-information-attestation', 'not-under-investigation-attestation', ] - military_records = [ - record for record in original_provider_data.get('militaryAffiliations', []) if record['status'] == 'active' - ] - if military_records: + + # Check if provider has active military status (tentative or approved) + # militaryStatus defaults to 'notApplicable' if not present + provider_military_status = original_provider_data.get('militaryStatus', 'notApplicable') + user_active_military = provider_military_status in ('tentative', 'approved') + + if user_active_military: + # Remove personal-information-home-state-attestation (not required for military users) + required_attestation_ids.remove('personal-information-home-state-attestation') + # Add military attestations required_attestation_ids.append('military-affiliation-confirmation-attestation') + required_attestation_ids.append('military-personal-information-state-license-attestation') + compact = original_provider_data.get('compact') attestations_from_system = [] for attestation_id in required_attestation_ids: From 2abec5fe9605ae7afa2edd94ac125a9c4bebaff5 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Tue, 30 Dec 2025 10:43:45 -0700 Subject: [PATCH 30/53] linter/formatting --- .../tests/smoke/purchasing_privileges_smoke_tests.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/compact-connect/tests/smoke/purchasing_privileges_smoke_tests.py b/backend/compact-connect/tests/smoke/purchasing_privileges_smoke_tests.py index ed3d2058c..26c7a0387 100644 --- a/backend/compact-connect/tests/smoke/purchasing_privileges_smoke_tests.py +++ b/backend/compact-connect/tests/smoke/purchasing_privileges_smoke_tests.py @@ -154,19 +154,19 @@ def test_purchasing_privilege(delete_current_privilege: bool = True): 'provision-of-true-information-attestation', 'not-under-investigation-attestation', ] - + # Check if provider has active military status (tentative or approved) # militaryStatus defaults to 'notApplicable' if not present provider_military_status = original_provider_data.get('militaryStatus', 'notApplicable') user_active_military = provider_military_status in ('tentative', 'approved') - + if user_active_military: # Remove personal-information-home-state-attestation (not required for military users) required_attestation_ids.remove('personal-information-home-state-attestation') # Add military attestations required_attestation_ids.append('military-affiliation-confirmation-attestation') required_attestation_ids.append('military-personal-information-state-license-attestation') - + compact = original_provider_data.get('compact') attestations_from_system = [] for attestation_id in required_attestation_ids: From 3d6aed53f2d487e8254dd486dd51ec82207c95da Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Tue, 30 Dec 2025 11:09:02 -0700 Subject: [PATCH 31/53] Add README for smoke test setup/running --- backend/compact-connect/tests/smoke/README.md | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 backend/compact-connect/tests/smoke/README.md diff --git a/backend/compact-connect/tests/smoke/README.md b/backend/compact-connect/tests/smoke/README.md new file mode 100644 index 000000000..7e065b1f1 --- /dev/null +++ b/backend/compact-connect/tests/smoke/README.md @@ -0,0 +1,149 @@ +# Smoke Tests + +This directory contains smoke tests for the Compact Connect API. Smoke tests are end-to-end integration tests that run against a test environment to verify that critical functionality works as expected. + +## Overview + +Smoke tests validate that key features of the Compact Connect API are working correctly in a test environment. They make real API calls and interact with actual AWS services (DynamoDB, Cognito, etc.) to ensure the system behaves correctly end-to-end. + +## Prerequisites + +Before running smoke tests, you must complete the following setup: + +### 1. Sandbox/Test Environment + +You must have access to a deployed sandbox environment of the Compact Connect API. The sandbox should be deployed with the following configuration: + +- **Security Profile**: Your `cdk.context.json` file must have `"security_profile": "VULNERABLE"` set. This allows the smoke tests to create users programmatically using the boto3 Cognito client. + +### 2. Registered Provider User + +You must have a registered provider user in your sandbox environment. This user will be used by the smoke tests to authenticate and perform various operations. + +**To create a registered provider user:** +1. Register a provider user through the normal registration flow in your sandbox environment +2. Ensure the user has at least one active license record +3. Note the user's email address and password - you'll need these for the environment variables + +### 3. AWS Credentials + +Ensure your AWS credentials are configured with appropriate permissions to: +- Access DynamoDB tables in the sandbox environment +- Access Cognito user pools in the sandbox environment +- Access other AWS services used by the smoke tests + +You can configure credentials using: +- AWS CLI: `aws configure` +- Environment variables: `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` +- IAM role (if running on EC2/ECS) + +### 4. Python Dependencies + +Install the required Python packages. The smoke tests use the same dependencies as the main codebase. Ensure you have: +- Python 3.x +- All dependencies from the project's requirements files + +## Environment Variables Setup + +1. **Copy the example environment file:** + ```bash + cp smoke_tests_env_example.json smoke_tests_env.json + ``` + +2. **Edit `smoke_tests_env.json`** with your sandbox environment values: + + **Required Variables:** + - `CC_TEST_API_BASE_URL`: Base URL for the Compact Connect API (e.g., `https://api.sandbox.compactconnect.org`) + - `CC_TEST_STATE_API_BASE_URL`: Base URL for the state API + - `CC_TEST_STATE_AUTH_URL`: OAuth2 token endpoint for state authentication + - `CC_TEST_COGNITO_STATE_AUTH_USER_POOL_ID`: Cognito user pool ID for state auth + - `CC_TEST_PROVIDER_DYNAMO_TABLE_NAME`: DynamoDB table name for provider data + - `CC_TEST_COMPACT_CONFIGURATION_DYNAMO_TABLE_NAME`: DynamoDB table name for compact configuration + - `CC_TEST_DATA_EVENT_DYNAMO_TABLE_NAME`: DynamoDB table name for data events + - `CC_TEST_STAFF_USER_DYNAMO_TABLE_NAME`: DynamoDB table name for staff users + - `CC_TEST_COGNITO_STAFF_USER_POOL_ID`: Cognito user pool ID for staff users + - `CC_TEST_COGNITO_STAFF_USER_POOL_CLIENT_ID`: Cognito client ID for staff users + - `CC_TEST_COGNITO_PROVIDER_USER_POOL_ID`: Cognito user pool ID for provider users + - `CC_TEST_COGNITO_PROVIDER_USER_POOL_CLIENT_ID`: Cognito client ID for provider users + - `CC_TEST_PROVIDER_USER_USERNAME`: Email address of your registered provider user + - `CC_TEST_PROVIDER_USER_PASSWORD`: Password for your registered provider user + - `ENVIRONMENT_NAME`: Name of your sandbox environment + - `AWS_DEFAULT_REGION`: AWS region where your sandbox is deployed (e.g., `us-east-1`) + + **Optional Variables (for specific tests):** + - `SANDBOX_AUTHORIZE_NET_API_LOGIN_ID`: Authorize.net API login ID for payment processing tests + - `SANDBOX_AUTHORIZE_NET_TRANSACTION_KEY`: Authorize.net transaction key for payment processing tests + - `CC_TEST_ROLLBACK_STEP_FUNCTION_ARN`: Step function ARN for rollback tests + - `CC_TEST_RATE_LIMITING_DYNAMO_TABLE_NAME`: DynamoDB table name for rate limiting + - `CC_TEST_SSN_DYNAMO_TABLE_NAME`: DynamoDB table name for SSN data + - `CC_TEST_GET_PROVIDER_SSN_LAMBDA_NAME`: Lambda function name for SSN retrieval + +3. **Important:** Never commit `smoke_tests_env.json` to version control. It contains sensitive credentials and should be in `.gitignore`. + +## Running Smoke Tests + +### Running Individual Test Files + +Each test file can be run independently from the compact-connect folder: + +```bash +# Navigate to the smoke tests directory +cd backend/compact-connect + +# Run a specific test file +python3 tests/smoke/purchasing_privileges_smoke_tests.py +python3 tests/smoke/military_affiliation_smoke_tests.py +python3 tests/smoke/query_provider_smoke_tests.py +``` + +## Special Test Requirements + +### Tests Requiring Manual Input + +Some tests require manual interaction: + +- **`practitioner_email_update_smoke_tests.py`**: Requires you to manually enter email verification codes sent to your email address. + +### Tests Creating Test Data + +Many tests create temporary test data (staff users, configurations, etc.) and clean it up automatically. However, if a test fails partway through, you may need to manually clean up test data. + +## Troubleshooting + +### Common Issues + +1. **"ResourceNotFoundException" when accessing DynamoDB tables** + - Verify that your `smoke_tests_env.json` has the correct table names for your sandbox environment + - Ensure your AWS credentials have permissions to access the tables + - Check that the tables exist in the specified region + +2. **"Failed to authenticate" or Cognito errors** + - Verify that `CC_TEST_PROVIDER_USER_USERNAME` and `CC_TEST_PROVIDER_USER_PASSWORD` are correct + - Ensure the provider user exists and is registered in your sandbox environment + - Check that `security_profile: "VULNERABLE"` is set in your `cdk.context.json` + + +### Verifying Test Data + +If a test fails, you can verify the state of your test data: + +1. Review CloudWatch logs for Lambda functions that were invoked +2. Check DynamoDB tables directly using the AWS Console or CLI +3. Check Cognito user pools to see if test users were created + +## Contributing + +When adding new smoke tests: + +1. Follow the existing pattern in other test files +2. Use `SmokeTestFailureException` for test failures +3. Include cleanup logic for any test data created +4. Add appropriate docstrings explaining what the test does +5. Update this README with information about your new test if there are any special requirements + +## Additional Resources + +- See individual test files for specific requirements and usage examples +- Check `smoke_common.py` for shared utilities and helper functions +- Review `config.py` to understand how environment variables are loaded + From 48179becfe0bfbde30d069d6219fc4e7b0bb4f2e Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Tue, 30 Dec 2025 11:12:41 -0700 Subject: [PATCH 32/53] Fix comment --- backend/compact-connect/tests/smoke/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/compact-connect/tests/smoke/README.md b/backend/compact-connect/tests/smoke/README.md index 7e065b1f1..2e98bb804 100644 --- a/backend/compact-connect/tests/smoke/README.md +++ b/backend/compact-connect/tests/smoke/README.md @@ -87,7 +87,7 @@ Install the required Python packages. The smoke tests use the same dependencies Each test file can be run independently from the compact-connect folder: ```bash -# Navigate to the smoke tests directory +# Navigate to the compact-connect directory cd backend/compact-connect # Run a specific test file From 30f20730f9b5b26d745bf159d5b442b94975124f Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Tue, 30 Dec 2025 14:47:41 -0700 Subject: [PATCH 33/53] PR feedback - add sso option to smoke test setup --- backend/compact-connect/tests/smoke/README.md | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/backend/compact-connect/tests/smoke/README.md b/backend/compact-connect/tests/smoke/README.md index 2e98bb804..8b3086a7d 100644 --- a/backend/compact-connect/tests/smoke/README.md +++ b/backend/compact-connect/tests/smoke/README.md @@ -32,10 +32,32 @@ Ensure your AWS credentials are configured with appropriate permissions to: - Access Cognito user pools in the sandbox environment - Access other AWS services used by the smoke tests -You can configure credentials using: -- AWS CLI: `aws configure` -- Environment variables: `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` -- IAM role (if running on EC2/ECS) +**Option A: Using AWS SSO** + +1. Configure your AWS profile to use SSO: + ```bash + aws configure sso + ``` + Follow the prompts to set up your SSO profile using the values from your IAM identity center login + (see https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html#sso-configure-profile-token-auto-sso) + +2. Log in to AWS SSO: + ```bash + aws sso login --profile + ``` + +3. Set your AWS profile environment variable (if not using the default profile): + ```bash + export AWS_PROFILE= + ``` + +**Option B: Using Environment Variables** + +1. Alternatively, you can use AWS CLI to configure credentials: + ```bash + aws configure + ``` + This will prompt you for your access key ID, secret access key, default region, and output format. ### 4. Python Dependencies From 6b37ff549569c72adb11c13a09082af03e8e5056 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Tue, 30 Dec 2025 14:55:20 -0700 Subject: [PATCH 34/53] PR feedback - split status API values from record values --- .../common/cc_common/data_model/data_client.py | 12 ++++++------ .../cc_common/data_model/provider_record_util.py | 4 ++-- .../common/cc_common/data_model/schema/common.py | 4 ++-- .../cc_common/data_model/schema/data_event/api.py | 6 +++++- .../common/cc_common/data_model/schema/fields.py | 7 +++---- .../data_model/schema/military_affiliation/api.py | 9 +++++++-- .../cc_common/data_model/schema/provider/api.py | 6 +++--- .../cc_common/data_model/schema/provider/record.py | 7 +++---- .../provider-data-v1/handlers/military_audit.py | 3 ++- .../test_handlers/test_provider_s3_events.py | 4 ++-- .../lambdas/python/purchases/handlers/privileges.py | 7 +++---- .../test_handlers/test_purchase_privileges.py | 12 ++++++++---- 12 files changed, 46 insertions(+), 35 deletions(-) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py index dc880023b..e40cae506 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py @@ -27,7 +27,7 @@ InvestigationStatusEnum, LicenseDeactivatedStatusEnum, LicenseEncumberedStatusEnum, - MilitaryAuditStatus, + MilitaryStatus, PrivilegeEncumberedStatusEnum, UpdateCategory, ) @@ -708,7 +708,7 @@ def complete_military_affiliation_initialization(self, compact: str, provider_id 'previous': provider_record.to_dict(), 'createDate': now, 'updatedValues': { - 'militaryStatus': MilitaryAuditStatus.TENTATIVE, + 'militaryStatus': MilitaryStatus.TENTATIVE, 'militaryStatusNote': '', }, } @@ -730,7 +730,7 @@ def complete_military_affiliation_initialization(self, compact: str, provider_id 'dateOfUpdate = :dateOfUpdate' ), 'ExpressionAttributeValues': { - ':militaryStatus': {'S': MilitaryAuditStatus.TENTATIVE}, + ':militaryStatus': {'S': MilitaryStatus.TENTATIVE}, ':militaryStatusNote': {'S': ''}, ':dateOfUpdate': {'S': self.config.current_standard_datetime.isoformat()}, }, @@ -748,9 +748,9 @@ def complete_military_affiliation_initialization(self, compact: str, provider_id # Update all military affiliation records for record in initializing_military_affiliation_records: if record.dateOfUpload == latest_military_affiliation_record.dateOfUpload: - record.update({'status': MilitaryAffiliationStatus.ACTIVE.value}) + status_value = MilitaryAffiliationStatus.ACTIVE.value else: - record.update({'status': MilitaryAffiliationStatus.INACTIVE.value}) + status_value = MilitaryAffiliationStatus.INACTIVE.value serialized_record = record.serialize_to_database_record() transaction_items.append( @@ -960,7 +960,7 @@ def process_military_audit( *, compact: str, provider_id: UUID, - military_status: MilitaryAuditStatus, + military_status: MilitaryStatus, military_status_note: str | None = None, ) -> None: """ diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py index 6bdc07c7c..173aa3a61 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py @@ -15,7 +15,7 @@ AdverseActionAgainstEnum, CompactEligibilityStatus, HomeJurisdictionChangeStatusEnum, - MilitaryAuditStatus, + MilitaryStatus, PrivilegeEncumberedStatusEnum, UpdateCategory, ) @@ -947,7 +947,7 @@ def generate_api_response_object(self) -> dict: # Set default values for military audit status fields if not present if 'militaryStatus' not in provider: - provider['militaryStatus'] = MilitaryAuditStatus.NOT_APPLICABLE.value + provider['militaryStatus'] = MilitaryStatus.NOT_APPLICABLE.value if 'militaryStatusNote' not in provider: provider['militaryStatusNote'] = '' diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py index d201a9676..6f83b4d7e 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py @@ -293,8 +293,8 @@ class InvestigationAgainstEnum(StrEnum): LICENSE = 'license' -class MilitaryAuditStatus(CCEnum): - """Status of military documentation audit by compact admins.""" +class MilitaryStatus(CCEnum): + """Status of military affiliation on top level provider record.""" NOT_APPLICABLE = 'notApplicable' TENTATIVE = 'tentative' diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/data_event/api.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/data_event/api.py index af32a6927..7b925dde3 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/data_event/api.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/data_event/api.py @@ -1,5 +1,6 @@ # ruff: noqa: N801, N815 invalid-name from cc_common.data_model.schema.base_record import ForgivingSchema +from cc_common.data_model.schema.common import MilitaryStatus from cc_common.data_model.schema.fields import ( Compact, Jurisdiction, @@ -91,6 +92,9 @@ class MilitaryAuditEventDetailSchema(ForgivingSchema): compact = Compact(required=True, allow_none=False) providerId = UUID(required=True, allow_none=False) - auditResult = String(required=True, allow_none=False, validate=OneOf(['approved', 'declined'])) + auditResult = String(required=True, allow_none=False, validate=OneOf([ + MilitaryStatus.APPROVED, + MilitaryStatus.DECLINED + ])) auditNote = String(required=False, allow_none=False) eventTime = DateTime(required=True, allow_none=False) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py index d1aa97415..f9e933fb3 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py @@ -12,7 +12,7 @@ InvestigationStatusEnum, LicenseDeactivatedStatusEnum, LicenseEncumberedStatusEnum, - MilitaryAuditStatus, + MilitaryStatus, PrivilegeEncumberedStatusEnum, UpdateCategory, ) @@ -133,10 +133,9 @@ def __init__(self, *args, **kwargs): super().__init__(*args, validate=OneOf([entry.value for entry in InvestigationAgainstEnum]), **kwargs) -class MilitaryAuditStatusField(String): +class MilitaryStatusField(String): def __init__(self, *args, **kwargs): - super().__init__(*args, validate=OneOf([entry.value for entry in MilitaryAuditStatus]), **kwargs) - + super().__init__(*args, validate=OneOf([entry.value for entry in MilitaryStatus]), **kwargs) class PositiveDecimal(Decimal): """A Decimal field that validates the value is greater than or equal to 0.""" diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/api.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/api.py index f10682878..1a6192246 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/api.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/api.py @@ -5,7 +5,7 @@ from cc_common.config import config from cc_common.data_model.schema.base_record import ForgivingSchema -from cc_common.data_model.schema.common import S3PresignedPostSchema +from cc_common.data_model.schema.common import S3PresignedPostSchema, CCEnum from cc_common.data_model.schema.military_affiliation.common import MilitaryAffiliationStatus, MilitaryAffiliationType @@ -23,11 +23,16 @@ class PostMilitaryAffiliationResponseSchema(ForgivingSchema): Nested(S3PresignedPostSchema(), required=True, allow_none=False), required=True, allow_none=False ) +class MilitaryAuditStatus(CCEnum): + """Status of military documentation audit by compact admins.""" + + APPROVED = 'approved' + DECLINED = 'declined' class MilitaryAuditRequestSchema(Schema): """Schema for validating military audit PATCH requests.""" - militaryStatus = String(required=True, allow_none=False, validate=OneOf(['approved', 'declined'])) + militaryStatus = String(required=True, allow_none=False, validate=OneOf([entry.value for entry in MilitaryAuditStatus])) militaryStatusNote = String(required=False, allow_none=False) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/api.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/api.py index 15a4f98a1..8b1715d56 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/api.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/api.py @@ -13,7 +13,7 @@ CompactEligibility, CurrentHomeJurisdictionField, Jurisdiction, - MilitaryAuditStatusField, + MilitaryStatusField, NationalProviderIdentifier, Set, SocialSecurityNumber, @@ -129,7 +129,7 @@ class ProviderReadPrivateResponseSchema(ForgivingSchema): ) # Military audit status fields - militaryStatus = MilitaryAuditStatusField(required=False, allow_none=False) + militaryStatus = MilitaryStatusField(required=False, allow_none=False) militaryStatusNote = String(required=False, allow_none=False) # these fields are specific to the read private role @@ -185,7 +185,7 @@ class ProviderGeneralResponseSchema(ForgivingSchema): militaryAffiliations = List(Nested(MilitaryAffiliationGeneralResponseSchema(), required=False, allow_none=False)) # Military audit status field (note is only available in readPrivate response) - militaryStatus = MilitaryAuditStatusField(required=False, allow_none=False) + militaryStatus = MilitaryStatusField(required=False, allow_none=False) class ProviderPublicResponseSchema(ForgivingSchema): diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py index 8bb127f2d..82082e9e9 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py @@ -22,10 +22,9 @@ CurrentHomeJurisdictionField, Jurisdiction, LicenseEncumberedStatusField, - MilitaryAuditStatusField, NationalProviderIdentifier, Set, - UpdateType, + UpdateType, MilitaryStatusField, ) from cc_common.data_model.update_tier_enum import UpdateTierEnum @@ -79,7 +78,7 @@ class ProviderRecordSchema(BaseRecordSchema): recoveryExpiry = DateTime(required=False, allow_none=False) # Military audit status fields - militaryStatus = MilitaryAuditStatusField(required=False, allow_none=False) + militaryStatus = MilitaryStatusField(required=False, allow_none=False) militaryStatusNote = String(required=False, allow_none=False) # Generated fields @@ -211,7 +210,7 @@ class ProviderUpdatePreviousRecordSchema(ForgivingSchema): dateOfExpiration = Date(required=True, allow_none=False) dateOfBirth = Date(required=True, allow_none=False) compactConnectRegisteredEmailAddress = Email(required=False, allow_none=False) - militaryStatus = MilitaryAuditStatusField(required=False, allow_none=False) + militaryStatus = MilitaryStatusField(required=False, allow_none=False) militaryStatusNote = String(required=False, allow_none=False) currentHomeJurisdiction = CurrentHomeJurisdictionField(required=False, allow_none=False) diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/military_audit.py b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/military_audit.py index 6bec15cd2..4b1d8e164 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/military_audit.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/military_audit.py @@ -2,7 +2,8 @@ from aws_lambda_powertools.utilities.typing import LambdaContext from cc_common.config import config, logger -from cc_common.data_model.schema.common import CCPermissionsAction, MilitaryAuditStatus +from cc_common.data_model.schema.common import CCPermissionsAction +from cc_common.data_model.schema.military_affiliation.api import MilitaryAuditStatus from cc_common.data_model.schema.military_affiliation.api import MilitaryAuditRequestSchema from cc_common.exceptions import CCInvalidRequestException from cc_common.utils import api_handler, authorize_compact_level_only_action, to_uuid diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_s3_events.py b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_s3_events.py index 820114062..c188c4b9f 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_s3_events.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_s3_events.py @@ -33,13 +33,13 @@ def _call_post_military_affiliation_endpoint(self): return provider_users_api_handler(event, self.mock_context) def _when_testing_military_affiliation_s3_object_create_event(self): - from cc_common.data_model.schema.common import MilitaryAuditStatus + from cc_common.data_model.schema.common import MilitaryStatus self.test_data_generator.put_default_provider_record_in_provider_table( value_overrides={ 'compact': TEST_COMPACT, 'providerId': TEST_PROVIDER_ID, - 'militaryStatus': MilitaryAuditStatus.DECLINED, + 'militaryStatus': MilitaryStatus.DECLINED, 'militaryStatusNote': 'some declined note', } ) diff --git a/backend/compact-connect/lambdas/python/purchases/handlers/privileges.py b/backend/compact-connect/lambdas/python/purchases/handlers/privileges.py index f734652a1..2922ecfd4 100644 --- a/backend/compact-connect/lambdas/python/purchases/handlers/privileges.py +++ b/backend/compact-connect/lambdas/python/purchases/handlers/privileges.py @@ -10,7 +10,7 @@ HomeJurisdictionChangeStatusEnum, LicenseDeactivatedStatusEnum, LicenseEncumberedStatusEnum, - MilitaryAuditStatus, + MilitaryStatus, ) from cc_common.data_model.schema.compact import Compact from cc_common.data_model.schema.compact.api import CompactOptionsResponseSchema @@ -351,11 +351,10 @@ def post_purchase_privileges(event: dict, context: LambdaContext): # noqa: ARG0 ) # Get militaryStatus from the provider record - # militaryStatus defaults to 'notApplicable' if not present (set in generate_api_response_object) - provider_military_status = top_level_provider_record.militaryStatus or MilitaryAuditStatus.NOT_APPLICABLE + provider_military_status = top_level_provider_record.militaryStatus # User is considered active military if their militaryStatus is either 'tentative' or 'approved' - user_active_military = provider_military_status in (MilitaryAuditStatus.TENTATIVE, MilitaryAuditStatus.APPROVED) + user_active_military = provider_military_status in (MilitaryStatus.TENTATIVE, MilitaryStatus.APPROVED) # Validate attestations are the latest versions before proceeding with the purchase _validate_attestations(compact_abbr, body.get('attestations', []), user_active_military) diff --git a/backend/compact-connect/lambdas/python/purchases/tests/function/test_handlers/test_purchase_privileges.py b/backend/compact-connect/lambdas/python/purchases/tests/function/test_handlers/test_purchase_privileges.py index 0cbf184c3..bbd1e486b 100644 --- a/backend/compact-connect/lambdas/python/purchases/tests/function/test_handlers/test_purchase_privileges.py +++ b/backend/compact-connect/lambdas/python/purchases/tests/function/test_handlers/test_purchase_privileges.py @@ -331,28 +331,32 @@ def test_post_purchase_privileges_calls_purchase_client_with_tentative_military_ self, mock_purchase_client_constructor ): """Test that tentative military status is considered active military.""" - self._when_testing_military_status(mock_purchase_client_constructor, 'tentative', True) + from cc_common.data_model.schema.common import MilitaryStatus + self._when_testing_military_status(mock_purchase_client_constructor, MilitaryStatus.TENTATIVE, True) @patch('handlers.privileges.PurchaseClient') def test_post_purchase_privileges_calls_purchase_client_with_approved_military_status( self, mock_purchase_client_constructor ): """Test that approved military status is considered active military.""" - self._when_testing_military_status(mock_purchase_client_constructor, 'approved', True) + from cc_common.data_model.schema.common import MilitaryStatus + self._when_testing_military_status(mock_purchase_client_constructor, MilitaryStatus.APPROVED, True) @patch('handlers.privileges.PurchaseClient') def test_post_purchase_privileges_calls_purchase_client_with_declined_military_status( self, mock_purchase_client_constructor ): """Test that declined military status is not considered active military.""" - self._when_testing_military_status(mock_purchase_client_constructor, 'declined', False) + from cc_common.data_model.schema.common import MilitaryStatus + self._when_testing_military_status(mock_purchase_client_constructor, MilitaryStatus.DECLINED, False) @patch('handlers.privileges.PurchaseClient') def test_post_purchase_privileges_calls_purchase_client_with_not_applicable_military_status( self, mock_purchase_client_constructor ): """Test that notApplicable military status is not considered active military.""" - self._when_testing_military_status(mock_purchase_client_constructor, 'notApplicable', False) + from cc_common.data_model.schema.common import MilitaryStatus + self._when_testing_military_status(mock_purchase_client_constructor, MilitaryStatus.NOT_APPLICABLE, False) @patch('handlers.privileges.PurchaseClient') def test_post_purchase_privileges_raises_exception_when_military_affiliation_in_initializing_status( From 7e52006b02b03e0572688659a6a7174271d49eb4 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Tue, 30 Dec 2025 14:55:31 -0700 Subject: [PATCH 35/53] PR feedback - use update instead of put --- .../common/cc_common/data_model/data_client.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py index e40cae506..d92fd6efe 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py @@ -755,9 +755,18 @@ def complete_military_affiliation_initialization(self, compact: str, provider_id serialized_record = record.serialize_to_database_record() transaction_items.append( { - 'Put': { + 'Update': { 'TableName': self.config.provider_table_name, - 'Item': TypeSerializer().serialize(serialized_record)['M'], + 'Key': { + 'pk': {'S': serialized_record['pk']}, + 'sk': {'S': serialized_record['sk']}, + }, + 'UpdateExpression': 'SET #status = :status, dateOfUpdate = :dateOfUpdate', + 'ExpressionAttributeNames': {'#status': 'status'}, + 'ExpressionAttributeValues': { + ':status': {'S': status_value}, + ':dateOfUpdate': {'S': self.config.current_standard_datetime.isoformat()}, + }, } } ) From bec0171958a7a48099283a49a4e2c2933134c3cd Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Tue, 30 Dec 2025 15:03:03 -0700 Subject: [PATCH 36/53] use audit status enum for api request schema --- .../cc_common/data_model/schema/data_event/api.py | 7 ++----- .../data_model/schema/military_affiliation/api.py | 11 +++-------- .../data_model/schema/military_affiliation/common.py | 7 +++++++ 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/data_event/api.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/data_event/api.py index 7b925dde3..1f0980b66 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/data_event/api.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/data_event/api.py @@ -1,6 +1,6 @@ # ruff: noqa: N801, N815 invalid-name from cc_common.data_model.schema.base_record import ForgivingSchema -from cc_common.data_model.schema.common import MilitaryStatus +from cc_common.data_model.schema.military_affiliation.common import MilitaryAuditStatus from cc_common.data_model.schema.fields import ( Compact, Jurisdiction, @@ -92,9 +92,6 @@ class MilitaryAuditEventDetailSchema(ForgivingSchema): compact = Compact(required=True, allow_none=False) providerId = UUID(required=True, allow_none=False) - auditResult = String(required=True, allow_none=False, validate=OneOf([ - MilitaryStatus.APPROVED, - MilitaryStatus.DECLINED - ])) + auditResult = String(required=True, allow_none=False, validate=OneOf([entry.value for entry in MilitaryAuditStatus])) auditNote = String(required=False, allow_none=False) eventTime = DateTime(required=True, allow_none=False) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/api.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/api.py index 1a6192246..fb8e64a78 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/api.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/api.py @@ -5,8 +5,9 @@ from cc_common.config import config from cc_common.data_model.schema.base_record import ForgivingSchema -from cc_common.data_model.schema.common import S3PresignedPostSchema, CCEnum -from cc_common.data_model.schema.military_affiliation.common import MilitaryAffiliationStatus, MilitaryAffiliationType +from cc_common.data_model.schema.common import S3PresignedPostSchema +from cc_common.data_model.schema.military_affiliation.common import MilitaryAffiliationStatus, MilitaryAffiliationType, \ + MilitaryAuditStatus class PostMilitaryAffiliationResponseSchema(ForgivingSchema): @@ -23,12 +24,6 @@ class PostMilitaryAffiliationResponseSchema(ForgivingSchema): Nested(S3PresignedPostSchema(), required=True, allow_none=False), required=True, allow_none=False ) -class MilitaryAuditStatus(CCEnum): - """Status of military documentation audit by compact admins.""" - - APPROVED = 'approved' - DECLINED = 'declined' - class MilitaryAuditRequestSchema(Schema): """Schema for validating military audit PATCH requests.""" diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/common.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/common.py index 7047e72a8..0e020f6e7 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/common.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/common.py @@ -12,6 +12,13 @@ class MilitaryAffiliationType(CCEnum): MILITARY_MEMBER_SPOUSE = 'militaryMemberSpouse' +class MilitaryAuditStatus(CCEnum): + """Status of military documentation audit by compact admins.""" + + APPROVED = 'approved' + DECLINED = 'declined' + + SUPPORTED_MILITARY_AFFILIATION_FILE_EXTENSIONS = ('pdf', 'jpg', 'jpeg', 'png', 'docx') MILITARY_AFFILIATIONS_DOCUMENT_TYPE_KEY_NAME = 'military-affiliations' From 593100199cab771cf4a4b36edccc7cc7950caef6 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Tue, 30 Dec 2025 15:04:06 -0700 Subject: [PATCH 37/53] formatting/linter --- .../cc_common/data_model/schema/data_event/api.py | 6 ++++-- .../common/cc_common/data_model/schema/fields.py | 1 + .../data_model/schema/military_affiliation/api.py | 12 +++++++++--- .../cc_common/data_model/schema/provider/record.py | 3 ++- .../provider-data-v1/handlers/military_audit.py | 3 +-- .../test_handlers/test_purchase_privileges.py | 4 ++++ 6 files changed, 21 insertions(+), 8 deletions(-) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/data_event/api.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/data_event/api.py index 1f0980b66..d761459fa 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/data_event/api.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/data_event/api.py @@ -1,10 +1,10 @@ # ruff: noqa: N801, N815 invalid-name from cc_common.data_model.schema.base_record import ForgivingSchema -from cc_common.data_model.schema.military_affiliation.common import MilitaryAuditStatus from cc_common.data_model.schema.fields import ( Compact, Jurisdiction, ) +from cc_common.data_model.schema.military_affiliation.common import MilitaryAuditStatus from marshmallow.fields import UUID, Date, DateTime, Email, List, Nested, String from marshmallow.validate import Length, OneOf @@ -92,6 +92,8 @@ class MilitaryAuditEventDetailSchema(ForgivingSchema): compact = Compact(required=True, allow_none=False) providerId = UUID(required=True, allow_none=False) - auditResult = String(required=True, allow_none=False, validate=OneOf([entry.value for entry in MilitaryAuditStatus])) + auditResult = String( + required=True, allow_none=False, validate=OneOf([entry.value for entry in MilitaryAuditStatus]) + ) auditNote = String(required=False, allow_none=False) eventTime = DateTime(required=True, allow_none=False) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py index f9e933fb3..62cf66a5d 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py @@ -137,6 +137,7 @@ class MilitaryStatusField(String): def __init__(self, *args, **kwargs): super().__init__(*args, validate=OneOf([entry.value for entry in MilitaryStatus]), **kwargs) + class PositiveDecimal(Decimal): """A Decimal field that validates the value is greater than or equal to 0.""" diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/api.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/api.py index fb8e64a78..3434a0782 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/api.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/api.py @@ -6,8 +6,11 @@ from cc_common.config import config from cc_common.data_model.schema.base_record import ForgivingSchema from cc_common.data_model.schema.common import S3PresignedPostSchema -from cc_common.data_model.schema.military_affiliation.common import MilitaryAffiliationStatus, MilitaryAffiliationType, \ - MilitaryAuditStatus +from cc_common.data_model.schema.military_affiliation.common import ( + MilitaryAffiliationStatus, + MilitaryAffiliationType, + MilitaryAuditStatus, +) class PostMilitaryAffiliationResponseSchema(ForgivingSchema): @@ -24,10 +27,13 @@ class PostMilitaryAffiliationResponseSchema(ForgivingSchema): Nested(S3PresignedPostSchema(), required=True, allow_none=False), required=True, allow_none=False ) + class MilitaryAuditRequestSchema(Schema): """Schema for validating military audit PATCH requests.""" - militaryStatus = String(required=True, allow_none=False, validate=OneOf([entry.value for entry in MilitaryAuditStatus])) + militaryStatus = String( + required=True, allow_none=False, validate=OneOf([entry.value for entry in MilitaryAuditStatus]) + ) militaryStatusNote = String(required=False, allow_none=False) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py index 82082e9e9..d6e51efc7 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py @@ -22,9 +22,10 @@ CurrentHomeJurisdictionField, Jurisdiction, LicenseEncumberedStatusField, + MilitaryStatusField, NationalProviderIdentifier, Set, - UpdateType, MilitaryStatusField, + UpdateType, ) from cc_common.data_model.update_tier_enum import UpdateTierEnum diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/military_audit.py b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/military_audit.py index 4b1d8e164..ad7dddaa7 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/military_audit.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/military_audit.py @@ -3,8 +3,7 @@ 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.military_affiliation.api import MilitaryAuditStatus -from cc_common.data_model.schema.military_affiliation.api import MilitaryAuditRequestSchema +from cc_common.data_model.schema.military_affiliation.api import MilitaryAuditRequestSchema, MilitaryAuditStatus from cc_common.exceptions import CCInvalidRequestException from cc_common.utils import api_handler, authorize_compact_level_only_action, to_uuid from marshmallow import ValidationError diff --git a/backend/compact-connect/lambdas/python/purchases/tests/function/test_handlers/test_purchase_privileges.py b/backend/compact-connect/lambdas/python/purchases/tests/function/test_handlers/test_purchase_privileges.py index bbd1e486b..c3d88f569 100644 --- a/backend/compact-connect/lambdas/python/purchases/tests/function/test_handlers/test_purchase_privileges.py +++ b/backend/compact-connect/lambdas/python/purchases/tests/function/test_handlers/test_purchase_privileges.py @@ -332,6 +332,7 @@ def test_post_purchase_privileges_calls_purchase_client_with_tentative_military_ ): """Test that tentative military status is considered active military.""" from cc_common.data_model.schema.common import MilitaryStatus + self._when_testing_military_status(mock_purchase_client_constructor, MilitaryStatus.TENTATIVE, True) @patch('handlers.privileges.PurchaseClient') @@ -340,6 +341,7 @@ def test_post_purchase_privileges_calls_purchase_client_with_approved_military_s ): """Test that approved military status is considered active military.""" from cc_common.data_model.schema.common import MilitaryStatus + self._when_testing_military_status(mock_purchase_client_constructor, MilitaryStatus.APPROVED, True) @patch('handlers.privileges.PurchaseClient') @@ -348,6 +350,7 @@ def test_post_purchase_privileges_calls_purchase_client_with_declined_military_s ): """Test that declined military status is not considered active military.""" from cc_common.data_model.schema.common import MilitaryStatus + self._when_testing_military_status(mock_purchase_client_constructor, MilitaryStatus.DECLINED, False) @patch('handlers.privileges.PurchaseClient') @@ -356,6 +359,7 @@ def test_post_purchase_privileges_calls_purchase_client_with_not_applicable_mili ): """Test that notApplicable military status is not considered active military.""" from cc_common.data_model.schema.common import MilitaryStatus + self._when_testing_military_status(mock_purchase_client_constructor, MilitaryStatus.NOT_APPLICABLE, False) @patch('handlers.privileges.PurchaseClient') From 15caa01b0292921448d3072b7f092b069739f5b2 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Tue, 30 Dec 2025 15:13:04 -0700 Subject: [PATCH 38/53] fix import --- .../python/data-events/handlers/military_audit_events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py b/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py index 78fff1ebd..3fc027072 100644 --- a/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py +++ b/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py @@ -1,7 +1,7 @@ from uuid import UUID from cc_common.config import config, logger -from cc_common.data_model.schema.common import MilitaryAuditStatus +from cc_common.data_model.schema.military_affiliation.common import MilitaryAuditStatus from cc_common.data_model.schema.data_event.api import MilitaryAuditEventDetailSchema from cc_common.event_state_client import EventType, NotificationTracker, RecipientType from cc_common.exceptions import CCInternalException From 3c54692719783304eaf7a87b529e2efafacfdbd1 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Tue, 30 Dec 2025 15:17:47 -0700 Subject: [PATCH 39/53] formatting --- .../python/data-events/handlers/military_audit_events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py b/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py index 3fc027072..f2098512d 100644 --- a/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py +++ b/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py @@ -1,8 +1,8 @@ from uuid import UUID from cc_common.config import config, logger -from cc_common.data_model.schema.military_affiliation.common import MilitaryAuditStatus from cc_common.data_model.schema.data_event.api import MilitaryAuditEventDetailSchema +from cc_common.data_model.schema.military_affiliation.common import MilitaryAuditStatus from cc_common.event_state_client import EventType, NotificationTracker, RecipientType from cc_common.exceptions import CCInternalException from cc_common.utils import sqs_handler_with_notification_tracking From aa6ef69855fdd12745cbf2eaf36bc47c5b18d542 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Tue, 30 Dec 2025 16:44:19 -0700 Subject: [PATCH 40/53] PR feedback - set militaryStatus as required --- .../cc_common/data_model/schema/provider/api.py | 4 ++-- .../data_model/schema/provider/record.py | 4 ++-- .../tests/resources/api/provider-response.json | 1 + .../common/tests/resources/dynamo/provider.json | 1 + .../python/provider-data-v1/handlers/providers.py | 1 + .../function/test_handlers/test_provider_users.py | 3 ++- .../function/test_handlers/test_public_lookup.py | 1 + .../function/test_handlers/test_registration.py | 1 + .../function/test_populate_provider_documents.py | 1 + .../tests/function/test_provider_update_ingest.py | 1 + .../tests/function/test_search_providers.py | 1 + .../stacks/api_stack/v1_api/api_model.py | 10 ++++++++++ .../snapshots/GET_PROVIDER_RESPONSE_SCHEMA.json | 15 +++++++++++++++ .../snapshots/PROVIDER_USER_RESPONSE_SCHEMA.json | 15 +++++++++++++++ .../QUERY_PROVIDERS_RESPONSE_SCHEMA.json | 15 +++++++++++++++ 15 files changed, 69 insertions(+), 5 deletions(-) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/api.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/api.py index 8b1715d56..c1013ff35 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/api.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/api.py @@ -129,7 +129,7 @@ class ProviderReadPrivateResponseSchema(ForgivingSchema): ) # Military audit status fields - militaryStatus = MilitaryStatusField(required=False, allow_none=False) + militaryStatus = MilitaryStatusField(required=True, allow_none=False) militaryStatusNote = String(required=False, allow_none=False) # these fields are specific to the read private role @@ -185,7 +185,7 @@ class ProviderGeneralResponseSchema(ForgivingSchema): militaryAffiliations = List(Nested(MilitaryAffiliationGeneralResponseSchema(), required=False, allow_none=False)) # Military audit status field (note is only available in readPrivate response) - militaryStatus = MilitaryStatusField(required=False, allow_none=False) + militaryStatus = MilitaryStatusField(required=True, allow_none=False) class ProviderPublicResponseSchema(ForgivingSchema): diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py index d6e51efc7..60f7b7693 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py @@ -12,7 +12,7 @@ ActiveInactiveStatus, ChangeHashMixin, CompactEligibilityStatus, - LicenseEncumberedStatusEnum, + LicenseEncumberedStatusEnum, MilitaryStatus, ) from cc_common.data_model.schema.fields import ( UNKNOWN_JURISDICTION, @@ -79,7 +79,7 @@ class ProviderRecordSchema(BaseRecordSchema): recoveryExpiry = DateTime(required=False, allow_none=False) # Military audit status fields - militaryStatus = MilitaryStatusField(required=False, allow_none=False) + militaryStatus = MilitaryStatusField(required=False, allow_none=False, load_default=MilitaryStatus.NOT_APPLICABLE) militaryStatusNote = String(required=False, allow_none=False) # Generated fields diff --git a/backend/compact-connect/lambdas/python/common/tests/resources/api/provider-response.json b/backend/compact-connect/lambdas/python/common/tests/resources/api/provider-response.json index b01a408e6..ea8f7e895 100644 --- a/backend/compact-connect/lambdas/python/common/tests/resources/api/provider-response.json +++ b/backend/compact-connect/lambdas/python/common/tests/resources/api/provider-response.json @@ -11,6 +11,7 @@ "jurisdictionUploadedCompactEligibility": "eligible", "compact": "aslp", "licenseJurisdiction": "oh", + "militaryStatus": "notApplicable", "privilegeJurisdictions": ["ne"], "compactConnectRegisteredEmailAddress": "björkRegisteredEmail@example.com", "dateOfUpdate": "2024-07-08T23:59:59+00:00", diff --git a/backend/compact-connect/lambdas/python/common/tests/resources/dynamo/provider.json b/backend/compact-connect/lambdas/python/common/tests/resources/dynamo/provider.json index 587f9cf49..6db386786 100644 --- a/backend/compact-connect/lambdas/python/common/tests/resources/dynamo/provider.json +++ b/backend/compact-connect/lambdas/python/common/tests/resources/dynamo/provider.json @@ -11,6 +11,7 @@ "givenName": "Björk", "middleName": "Gunnar", "familyName": "Guðmundsdóttir", + "militaryStatus": "notApplicable", "licenseJurisdiction": "oh", "privilegeJurisdictions": ["ne"], "jurisdictionUploadedLicenseStatus": "active", diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/providers.py b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/providers.py index 18c1e83e1..62e14fb1b 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/providers.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/providers.py @@ -4,6 +4,7 @@ from botocore.exceptions import ClientError from cc_common.config import config, logger, metrics from cc_common.data_model.schema.common import CCPermissionsAction +from cc_common.data_model.schema.provider import ProviderData from cc_common.data_model.schema.provider.api import ( ProviderGeneralResponseSchema, ProviderSSNResponseSchema, diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users.py b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users.py index 978d28c37..c54cd4731 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users.py @@ -433,6 +433,7 @@ def test_patch_provider_military_affiliation_updates_status_when_initializing(se def test_patch_provider_military_affiliation_removes_military_status_fields(self): """Test that ending military affiliation removes militaryStatus and militaryStatusNote from provider record.""" from handlers.provider_users import provider_users_api_handler + from cc_common.data_model.schema.common import MilitaryStatus # Create provider with declined military status and a note test_provider = self.test_data_generator.put_default_provider_record_in_provider_table( @@ -451,7 +452,7 @@ def test_patch_provider_military_affiliation_removes_military_status_fields(self compact=test_provider.compact, provider_id=test_provider.providerId ) - self.assertIsNone(updated_provider_record.militaryStatus) + self.assertEqual(MilitaryStatus.NOT_APPLICABLE, updated_provider_record.militaryStatus) self.assertIsNone(updated_provider_record.militaryStatusNote) def test_patch_provider_military_affiliation_creates_provider_update_record(self): diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_public_lookup.py b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_public_lookup.py index 1c8884811..8b33a3182 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_public_lookup.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_public_lookup.py @@ -44,6 +44,7 @@ def test_public_query_by_provider_id_returns_public_allowed_fields(self): expected_provider.pop('dateOfExpiration') expected_provider.pop('jurisdictionUploadedLicenseStatus') expected_provider.pop('jurisdictionUploadedCompactEligibility') + expected_provider.pop('militaryStatus') body = json.loads(resp['body']) self.assertEqual( diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_registration.py b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_registration.py index f0e4ed6ae..2e0f3d379 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_registration.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_registration.py @@ -839,6 +839,7 @@ def test_registration_creates_provider_update_record(self, mock_verify_recaptcha 'npi': provider_data.npi, 'providerId': provider_data.providerId, 'ssnLastFour': provider_data.ssnLastFour, + 'militaryStatus': 'notApplicable', }, 'providerId': provider_data.providerId, 'type': 'providerUpdate', diff --git a/backend/compact-connect/lambdas/python/search/tests/function/test_populate_provider_documents.py b/backend/compact-connect/lambdas/python/search/tests/function/test_populate_provider_documents.py index 932f79cb3..234c4ca54 100644 --- a/backend/compact-connect/lambdas/python/search/tests/function/test_populate_provider_documents.py +++ b/backend/compact-connect/lambdas/python/search/tests/function/test_populate_provider_documents.py @@ -110,6 +110,7 @@ def _generate_expected_call_for_document(self, compact): 'compactConnectRegisteredEmailAddress': DEFAULT_REGISTERED_EMAIL_ADDRESS, 'jurisdictionUploadedLicenseStatus': 'active', 'jurisdictionUploadedCompactEligibility': 'eligible', + 'militaryStatus': 'notApplicable', 'privilegeJurisdictions': ['ne'], 'birthMonthDay': '06-06', 'licenses': [ diff --git a/backend/compact-connect/lambdas/python/search/tests/function/test_provider_update_ingest.py b/backend/compact-connect/lambdas/python/search/tests/function/test_provider_update_ingest.py index cdd04a12c..8317a31e4 100644 --- a/backend/compact-connect/lambdas/python/search/tests/function/test_provider_update_ingest.py +++ b/backend/compact-connect/lambdas/python/search/tests/function/test_provider_update_ingest.py @@ -145,6 +145,7 @@ def _generate_expected_document(self, compact: str, provider_id: str = None) -> 'compactConnectRegisteredEmailAddress': DEFAULT_REGISTERED_EMAIL_ADDRESS, 'jurisdictionUploadedLicenseStatus': 'active', 'jurisdictionUploadedCompactEligibility': 'eligible', + 'militaryStatus': 'notApplicable', 'privilegeJurisdictions': ['ne'], 'birthMonthDay': '06-06', 'licenses': [ diff --git a/backend/compact-connect/lambdas/python/search/tests/function/test_search_providers.py b/backend/compact-connect/lambdas/python/search/tests/function/test_search_providers.py index 63860110b..5e4ef2dd7 100644 --- a/backend/compact-connect/lambdas/python/search/tests/function/test_search_providers.py +++ b/backend/compact-connect/lambdas/python/search/tests/function/test_search_providers.py @@ -283,6 +283,7 @@ def test_search_returns_sanitized_providers(self, mock_opensearch_client): 'jurisdictionUploadedCompactEligibility': 'eligible', 'jurisdictionUploadedLicenseStatus': 'active', 'licenseJurisdiction': 'oh', + 'militaryStatus': 'notApplicable', 'licenseStatus': 'active', 'privilegeJurisdictions': [], 'providerId': '00000000-0000-0000-0000-000000000001', diff --git a/backend/compact-connect/stacks/api_stack/v1_api/api_model.py b/backend/compact-connect/stacks/api_stack/v1_api/api_model.py index e06d5d341..a2b741296 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/api_model.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/api_model.py @@ -1553,6 +1553,16 @@ def _common_provider_properties(self) -> dict: ), 'currentHomeJurisdiction': self.current_home_jurisdiction_selection_field, 'dateOfUpdate': JsonSchema(type=JsonSchemaType.STRING, format='date-time'), + 'militaryStatus': JsonSchema( + type=JsonSchemaType.STRING, + enum=['notApplicable', 'tentative', 'approved', 'declined'], + description='Status of military affiliation on the provider record', + ), + 'militaryStatusNote': JsonSchema( + type=JsonSchemaType.STRING, + description='Optional note about the military status (typically for declines)', + max_length=5000, + ), } @property diff --git a/backend/compact-connect/tests/resources/snapshots/GET_PROVIDER_RESPONSE_SCHEMA.json b/backend/compact-connect/tests/resources/snapshots/GET_PROVIDER_RESPONSE_SCHEMA.json index 7c87c0372..a03082b39 100644 --- a/backend/compact-connect/tests/resources/snapshots/GET_PROVIDER_RESPONSE_SCHEMA.json +++ b/backend/compact-connect/tests/resources/snapshots/GET_PROVIDER_RESPONSE_SCHEMA.json @@ -2271,6 +2271,21 @@ "dateOfUpdate": { "format": "date-time", "type": "string" + }, + "militaryStatus": { + "description": "Status of military affiliation on the provider record", + "enum": [ + "notApplicable", + "tentative", + "approved", + "declined" + ], + "type": "string" + }, + "militaryStatusNote": { + "description": "Optional note about the military status (typically for declines)", + "maxLength": 5000, + "type": "string" } }, "required": [ diff --git a/backend/compact-connect/tests/resources/snapshots/PROVIDER_USER_RESPONSE_SCHEMA.json b/backend/compact-connect/tests/resources/snapshots/PROVIDER_USER_RESPONSE_SCHEMA.json index 7c87c0372..a03082b39 100644 --- a/backend/compact-connect/tests/resources/snapshots/PROVIDER_USER_RESPONSE_SCHEMA.json +++ b/backend/compact-connect/tests/resources/snapshots/PROVIDER_USER_RESPONSE_SCHEMA.json @@ -2271,6 +2271,21 @@ "dateOfUpdate": { "format": "date-time", "type": "string" + }, + "militaryStatus": { + "description": "Status of military affiliation on the provider record", + "enum": [ + "notApplicable", + "tentative", + "approved", + "declined" + ], + "type": "string" + }, + "militaryStatusNote": { + "description": "Optional note about the military status (typically for declines)", + "maxLength": 5000, + "type": "string" } }, "required": [ diff --git a/backend/compact-connect/tests/resources/snapshots/QUERY_PROVIDERS_RESPONSE_SCHEMA.json b/backend/compact-connect/tests/resources/snapshots/QUERY_PROVIDERS_RESPONSE_SCHEMA.json index 0deae9cb6..dc59ce1ce 100644 --- a/backend/compact-connect/tests/resources/snapshots/QUERY_PROVIDERS_RESPONSE_SCHEMA.json +++ b/backend/compact-connect/tests/resources/snapshots/QUERY_PROVIDERS_RESPONSE_SCHEMA.json @@ -281,6 +281,21 @@ "dateOfUpdate": { "format": "date-time", "type": "string" + }, + "militaryStatus": { + "description": "Status of military affiliation on the provider record", + "enum": [ + "notApplicable", + "tentative", + "approved", + "declined" + ], + "type": "string" + }, + "militaryStatusNote": { + "description": "Optional note about the military status (typically for declines)", + "maxLength": 5000, + "type": "string" } }, "required": [ From 736ea6e6875e241e0b9741984cb5c154b2e4fbfb Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Tue, 30 Dec 2025 16:45:20 -0700 Subject: [PATCH 41/53] formatting --- .../common/cc_common/data_model/schema/provider/record.py | 3 ++- .../lambdas/python/provider-data-v1/handlers/providers.py | 1 - .../tests/function/test_handlers/test_provider_users.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py index 60f7b7693..2857e7a39 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py @@ -12,7 +12,8 @@ ActiveInactiveStatus, ChangeHashMixin, CompactEligibilityStatus, - LicenseEncumberedStatusEnum, MilitaryStatus, + LicenseEncumberedStatusEnum, + MilitaryStatus, ) from cc_common.data_model.schema.fields import ( UNKNOWN_JURISDICTION, diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/providers.py b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/providers.py index 62e14fb1b..18c1e83e1 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/providers.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/providers.py @@ -4,7 +4,6 @@ from botocore.exceptions import ClientError from cc_common.config import config, logger, metrics from cc_common.data_model.schema.common import CCPermissionsAction -from cc_common.data_model.schema.provider import ProviderData from cc_common.data_model.schema.provider.api import ( ProviderGeneralResponseSchema, ProviderSSNResponseSchema, diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users.py b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users.py index c54cd4731..bf4f22a28 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users.py @@ -432,8 +432,8 @@ def test_patch_provider_military_affiliation_updates_status_when_initializing(se def test_patch_provider_military_affiliation_removes_military_status_fields(self): """Test that ending military affiliation removes militaryStatus and militaryStatusNote from provider record.""" - from handlers.provider_users import provider_users_api_handler from cc_common.data_model.schema.common import MilitaryStatus + from handlers.provider_users import provider_users_api_handler # Create provider with declined military status and a note test_provider = self.test_data_generator.put_default_provider_record_in_provider_table( From f4f5c373eae4f33ffbc22cf3e0795e8c6e93a2a8 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Tue, 30 Dec 2025 17:11:28 -0700 Subject: [PATCH 42/53] set default values on API response schema --- backend/compact-connect/bin/run_python_tests.py | 1 + .../common/cc_common/data_model/provider_record_util.py | 6 ------ .../common/cc_common/data_model/schema/provider/api.py | 8 ++++---- .../common/cc_common/data_model/schema/provider/record.py | 2 +- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/backend/compact-connect/bin/run_python_tests.py b/backend/compact-connect/bin/run_python_tests.py index 2d35ad977..22270123c 100755 --- a/backend/compact-connect/bin/run_python_tests.py +++ b/backend/compact-connect/bin/run_python_tests.py @@ -30,6 +30,7 @@ 'lambdas/python/disaster-recovery', 'lambdas/python/migration', 'lambdas/python/provider-data-v1', + 'lambdas/python/search', 'lambdas/python/staff-user-pre-token', 'lambdas/python/staff-users', '.', # CDK tests diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py index 173aa3a61..6e5f90f62 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py @@ -945,10 +945,4 @@ def generate_api_response_object(self) -> dict: provider['privileges'] = privileges provider['militaryAffiliations'] = military_affiliations - # Set default values for military audit status fields if not present - if 'militaryStatus' not in provider: - provider['militaryStatus'] = MilitaryStatus.NOT_APPLICABLE.value - if 'militaryStatusNote' not in provider: - provider['militaryStatusNote'] = '' - return provider diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/api.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/api.py index c1013ff35..82dc88761 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/api.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/api.py @@ -6,7 +6,7 @@ from marshmallow.validate import Length, OneOf, Range, Regexp from cc_common.data_model.schema.base_record import ForgivingSchema -from cc_common.data_model.schema.common import CCRequestSchema +from cc_common.data_model.schema.common import CCRequestSchema, MilitaryStatus from cc_common.data_model.schema.fields import ( ActiveInactive, Compact, @@ -129,8 +129,8 @@ class ProviderReadPrivateResponseSchema(ForgivingSchema): ) # Military audit status fields - militaryStatus = MilitaryStatusField(required=True, allow_none=False) - militaryStatusNote = String(required=False, allow_none=False) + militaryStatus = MilitaryStatusField(required=False, allow_none=False, load_default=MilitaryStatus.NOT_APPLICABLE) + militaryStatusNote = String(required=False, allow_none=False, load_default='') # these fields are specific to the read private role dateOfBirth = Raw(required=True, allow_none=False) @@ -185,7 +185,7 @@ class ProviderGeneralResponseSchema(ForgivingSchema): militaryAffiliations = List(Nested(MilitaryAffiliationGeneralResponseSchema(), required=False, allow_none=False)) # Military audit status field (note is only available in readPrivate response) - militaryStatus = MilitaryStatusField(required=True, allow_none=False) + militaryStatus = MilitaryStatusField(required=False, allow_none=False, load_default=MilitaryStatus.NOT_APPLICABLE) class ProviderPublicResponseSchema(ForgivingSchema): diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py index 2857e7a39..179264853 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py @@ -80,7 +80,7 @@ class ProviderRecordSchema(BaseRecordSchema): recoveryExpiry = DateTime(required=False, allow_none=False) # Military audit status fields - militaryStatus = MilitaryStatusField(required=False, allow_none=False, load_default=MilitaryStatus.NOT_APPLICABLE) + militaryStatus = MilitaryStatusField(required=False, allow_none=False) militaryStatusNote = String(required=False, allow_none=False) # Generated fields From db1fcec2d2e5d097091a1ec4036d9e0f8ef62f1a Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Tue, 30 Dec 2025 17:31:51 -0700 Subject: [PATCH 43/53] Fix tests to account for api default setting --- .../common/cc_common/data_model/schema/provider/record.py | 1 - .../lambdas/python/common/common_test/test_data_generator.py | 5 ++++- .../python/common/tests/resources/dynamo/provider.json | 1 - .../tests/function/test_handlers/test_provider_users.py | 2 +- .../tests/function/test_handlers/test_registration.py | 1 - 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py index 179264853..d6e51efc7 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py @@ -13,7 +13,6 @@ ChangeHashMixin, CompactEligibilityStatus, LicenseEncumberedStatusEnum, - MilitaryStatus, ) from cc_common.data_model.schema.fields import ( UNKNOWN_JURISDICTION, diff --git a/backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py b/backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py index e4fb39ffc..6ce386065 100644 --- a/backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py +++ b/backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py @@ -613,7 +613,10 @@ def generate_default_provider_detail_response(provider_record_items: list[CCData default_military_affiliation, datetime.fromisoformat(DEFAULT_MILITARY_UPDATE_DATE) ) - provider_record = TestDataGenerator.generate_default_provider().serialize_to_database_record() + provider_record = TestDataGenerator.generate_default_provider(value_overrides={ + 'militaryStatus': 'notApplicable', + 'militaryStatusNote': '', + }).serialize_to_database_record() provider_record['dateOfUpdate'] = DEFAULT_PROVIDER_UPDATE_DATETIME license_record = default_license_record.serialize_to_database_record() license_record['dateOfUpdate'] = DEFAULT_LICENSE_UPDATE_DATETIME diff --git a/backend/compact-connect/lambdas/python/common/tests/resources/dynamo/provider.json b/backend/compact-connect/lambdas/python/common/tests/resources/dynamo/provider.json index 6db386786..587f9cf49 100644 --- a/backend/compact-connect/lambdas/python/common/tests/resources/dynamo/provider.json +++ b/backend/compact-connect/lambdas/python/common/tests/resources/dynamo/provider.json @@ -11,7 +11,6 @@ "givenName": "Björk", "middleName": "Gunnar", "familyName": "Guðmundsdóttir", - "militaryStatus": "notApplicable", "licenseJurisdiction": "oh", "privilegeJurisdictions": ["ne"], "jurisdictionUploadedLicenseStatus": "active", diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users.py b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users.py index bf4f22a28..fadb1fa31 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users.py @@ -452,7 +452,7 @@ def test_patch_provider_military_affiliation_removes_military_status_fields(self compact=test_provider.compact, provider_id=test_provider.providerId ) - self.assertEqual(MilitaryStatus.NOT_APPLICABLE, updated_provider_record.militaryStatus) + self.assertIsNone(updated_provider_record.militaryStatus) self.assertIsNone(updated_provider_record.militaryStatusNote) def test_patch_provider_military_affiliation_creates_provider_update_record(self): diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_registration.py b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_registration.py index 2e0f3d379..f0e4ed6ae 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_registration.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_registration.py @@ -839,7 +839,6 @@ def test_registration_creates_provider_update_record(self, mock_verify_recaptcha 'npi': provider_data.npi, 'providerId': provider_data.providerId, 'ssnLastFour': provider_data.ssnLastFour, - 'militaryStatus': 'notApplicable', }, 'providerId': provider_data.providerId, 'type': 'providerUpdate', From 5321cfd9c8c04ac7c73e7bc276852ae94deaa34f Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Mon, 5 Jan 2026 09:57:38 -0600 Subject: [PATCH 44/53] linter/formatter --- .../cc_common/data_model/provider_record_util.py | 1 - .../data_model/schema/military_affiliation/api.py | 4 ++-- .../python/common/common_test/test_data_generator.py | 10 ++++++---- .../function/test_handlers/test_provider_users.py | 1 - 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py index 6e5f90f62..dd50a2252 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py @@ -15,7 +15,6 @@ AdverseActionAgainstEnum, CompactEligibilityStatus, HomeJurisdictionChangeStatusEnum, - MilitaryStatus, PrivilegeEncumberedStatusEnum, UpdateCategory, ) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/api.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/api.py index 3434a0782..fe7289659 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/api.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/api.py @@ -1,7 +1,7 @@ # ruff: noqa: N801, N815, ARG002 invalid-name unused-argument from marshmallow import Schema from marshmallow.fields import Dict, List, Nested, Raw, String -from marshmallow.validate import OneOf +from marshmallow.validate import Length, OneOf from cc_common.config import config from cc_common.data_model.schema.base_record import ForgivingSchema @@ -34,7 +34,7 @@ class MilitaryAuditRequestSchema(Schema): militaryStatus = String( required=True, allow_none=False, validate=OneOf([entry.value for entry in MilitaryAuditStatus]) ) - militaryStatusNote = String(required=False, allow_none=False) + militaryStatusNote = String(required=False, allow_none=False, validate=Length(min=2, max=1000)) class MilitaryAffiliationGeneralResponseSchema(ForgivingSchema): diff --git a/backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py b/backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py index 6ce386065..55fdf3d3d 100644 --- a/backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py +++ b/backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py @@ -613,10 +613,12 @@ def generate_default_provider_detail_response(provider_record_items: list[CCData default_military_affiliation, datetime.fromisoformat(DEFAULT_MILITARY_UPDATE_DATE) ) - provider_record = TestDataGenerator.generate_default_provider(value_overrides={ - 'militaryStatus': 'notApplicable', - 'militaryStatusNote': '', - }).serialize_to_database_record() + provider_record = TestDataGenerator.generate_default_provider( + value_overrides={ + 'militaryStatus': 'notApplicable', + 'militaryStatusNote': '', + } + ).serialize_to_database_record() provider_record['dateOfUpdate'] = DEFAULT_PROVIDER_UPDATE_DATETIME license_record = default_license_record.serialize_to_database_record() license_record['dateOfUpdate'] = DEFAULT_LICENSE_UPDATE_DATETIME diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users.py b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users.py index fadb1fa31..978d28c37 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users.py @@ -432,7 +432,6 @@ def test_patch_provider_military_affiliation_updates_status_when_initializing(se def test_patch_provider_military_affiliation_removes_military_status_fields(self): """Test that ending military affiliation removes militaryStatus and militaryStatusNote from provider record.""" - from cc_common.data_model.schema.common import MilitaryStatus from handlers.provider_users import provider_users_api_handler # Create provider with declined military status and a note From e360a7dde5165e86e1d8e4c2c484a79af3c5e991 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Wed, 7 Jan 2026 09:47:52 -0600 Subject: [PATCH 45/53] reword email templates according to feedback --- .../nodejs/lib/email/email-notification-service.ts | 10 +++++----- .../nodejs/tests/email-notification-service.test.ts | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/backend/compact-connect/lambdas/nodejs/lib/email/email-notification-service.ts b/backend/compact-connect/lambdas/nodejs/lib/email/email-notification-service.ts index e7f75dd5b..00edc16e1 100644 --- a/backend/compact-connect/lambdas/nodejs/lib/email/email-notification-service.ts +++ b/backend/compact-connect/lambdas/nodejs/lib/email/email-notification-service.ts @@ -511,8 +511,8 @@ export class EmailNotificationService extends BaseEmailService { } const report = this.getNewEmailTemplate(); - const subject = 'Military Documentation Approved - Compact Connect'; - const bodyText = 'This message is to notify you that your military documentation has been reviewed and approved by the compact administration.'; + const subject = 'Military Status Documentation Approved - Compact Connect'; + const bodyText = 'This message is to notify you that your military status documentation has been reviewed and approved by the compact staff.'; this.insertHeader(report, subject); this.insertBody(report, bodyText); @@ -543,11 +543,11 @@ export class EmailNotificationService extends BaseEmailService { } const report = this.getNewEmailTemplate(); - const subject = 'Military Documentation Declined - Compact Connect'; - let bodyText = 'This message is to notify you that your military documentation has been reviewed and declined by the compact administration.'; + const subject = 'Military Status Documentation Declined - Compact Connect'; + let bodyText = 'This message is to notify you that your military status documentation has been reviewed and declined by the compact staff.'; if (auditNote && auditNote.trim().length > 0) { - bodyText += `\n\nMessage from the compact administration: ${auditNote}`; + bodyText += `\n\nMessage from the compact staff: ${auditNote}`; } this.insertHeader(report, subject); diff --git a/backend/compact-connect/lambdas/nodejs/tests/email-notification-service.test.ts b/backend/compact-connect/lambdas/nodejs/tests/email-notification-service.test.ts index 4b77f5719..717a07e0a 100644 --- a/backend/compact-connect/lambdas/nodejs/tests/email-notification-service.test.ts +++ b/backend/compact-connect/lambdas/nodejs/tests/email-notification-service.test.ts @@ -1534,7 +1534,7 @@ describe('EmailNotificationServiceLambda', () => { }, Subject: { Charset: 'UTF-8', - Data: 'Military Documentation Approved - Compact Connect' + Data: 'Military Status Documentation Approved - Compact Connect' } } }, @@ -1546,8 +1546,8 @@ describe('EmailNotificationServiceLambda', () => { const htmlContent = emailCall.args[0].input.Content?.Simple?.Body?.Html?.Data; expect(htmlContent).toBeDefined(); - expect(htmlContent).toContain('This message is to notify you that your military documentation has been reviewed and approved by the compact administration.'); - expect(htmlContent).toContain('Military Documentation Approved - Compact Connect'); + expect(htmlContent).toContain('This message is to notify you that your military status documentation has been reviewed and approved by the compact staff.'); + expect(htmlContent).toContain('Military Status Documentation Approved - Compact Connect'); }); it('should throw error when no recipients found', async () => { @@ -1595,7 +1595,7 @@ describe('EmailNotificationServiceLambda', () => { }, Subject: { Charset: 'UTF-8', - Data: 'Military Documentation Declined - Compact Connect' + Data: 'Military Status Documentation Declined - Compact Connect' } } }, @@ -1607,8 +1607,8 @@ describe('EmailNotificationServiceLambda', () => { const htmlContent = emailCall.args[0].input.Content?.Simple?.Body?.Html?.Data; expect(htmlContent).toBeDefined(); - expect(htmlContent).toContain('This message is to notify you that your military documentation has been reviewed and declined by the compact administration.'); - expect(htmlContent).toContain('Military Documentation Declined - Compact Connect'); + expect(htmlContent).toContain('This message is to notify you that your military status documentation has been reviewed and declined by the compact staff.'); + expect(htmlContent).toContain('Military Status Documentation Declined - Compact Connect'); expect(htmlContent).toContain('The documentation provided was incomplete and did not meet the required standards.'); }); From 9158260c32cb6f82f0140f12d6a7275544e869a8 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Mon, 12 Jan 2026 21:30:15 -0600 Subject: [PATCH 46/53] PR feedback - minor fixes and comments --- .../lambdas/python/common/cc_common/data_model/data_client.py | 3 +++ .../common/cc_common/data_model/schema/provider/__init__.py | 3 --- .../python/data-events/handlers/military_audit_events.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py index d92fd6efe..b250176b2 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py @@ -992,6 +992,9 @@ def process_military_audit( # Get provider records provider_user_records = self.get_provider_user_records(compact=compact, provider_id=provider_id) provider_record = provider_user_records.get_provider_record() + # The point of this check is not to see what the status of their last military affiliation document is, + # but rather to verify that they have a military affiliation at all. If they don't, then this returns None, + # and we error out with a 404 status code. latest_military_affiliation = provider_user_records.get_latest_military_affiliation() if not latest_military_affiliation: diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/__init__.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/__init__.py index 2c4b5a13f..a93558942 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/__init__.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/__init__.py @@ -153,7 +153,6 @@ def militaryStatus(self) -> str | None: The military audit status of the provider. Possible values: 'notApplicable', 'tentative', 'approved', 'declined' - Default is 'notApplicable' if no military documentation has been uploaded. """ return self._data.get('militaryStatus') @@ -161,8 +160,6 @@ def militaryStatus(self) -> str | None: def militaryStatusNote(self) -> str | None: """ The note from the most recent military audit decision (if declined). - - Empty string if no note has been provided. """ return self._data.get('militaryStatusNote') diff --git a/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py b/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py index f2098512d..c473012db 100644 --- a/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py +++ b/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py @@ -34,7 +34,7 @@ def military_audit_notification_listener(message: dict, tracker: NotificationTra with logger.append_context_keys( compact=compact, - provider_id=str(provider_id), + provider_id=provider_id, audit_result=audit_result, ): logger.info('Processing military audit notification') @@ -43,7 +43,7 @@ def military_audit_notification_listener(message: dict, tracker: NotificationTra try: provider_records = config.data_client.get_provider_user_records( compact=compact, - provider_id=UUID(str(provider_id)), + provider_id=provider_id, ) provider_record = provider_records.get_provider_record() except Exception as e: From 3b684f4a150dee0cc863efab34c5026dd81b4bba Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Mon, 12 Jan 2026 21:46:26 -0600 Subject: [PATCH 47/53] PR feedback - smoke test README --- backend/compact-connect/tests/smoke/README.md | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/backend/compact-connect/tests/smoke/README.md b/backend/compact-connect/tests/smoke/README.md index 8b3086a7d..ce8c173a1 100644 --- a/backend/compact-connect/tests/smoke/README.md +++ b/backend/compact-connect/tests/smoke/README.md @@ -32,8 +32,6 @@ Ensure your AWS credentials are configured with appropriate permissions to: - Access Cognito user pools in the sandbox environment - Access other AWS services used by the smoke tests -**Option A: Using AWS SSO** - 1. Configure your AWS profile to use SSO: ```bash aws configure sso @@ -50,15 +48,6 @@ Ensure your AWS credentials are configured with appropriate permissions to: ```bash export AWS_PROFILE= ``` - -**Option B: Using Environment Variables** - -1. Alternatively, you can use AWS CLI to configure credentials: - ```bash - aws configure - ``` - This will prompt you for your access key ID, secret access key, default region, and output format. - ### 4. Python Dependencies Install the required Python packages. The smoke tests use the same dependencies as the main codebase. Ensure you have: @@ -145,9 +134,9 @@ Many tests create temporary test data (staff users, configurations, etc.) and cl - Check that `security_profile: "VULNERABLE"` is set in your `cdk.context.json` -### Verifying Test Data +### Triage Test Failures -If a test fails, you can verify the state of your test data: +If a test fails, you can consider the following steps to triage the cause of the failures: 1. Review CloudWatch logs for Lambda functions that were invoked 2. Check DynamoDB tables directly using the AWS Console or CLI From 4b18e602cbe767cdd6af65172ade458e98eec029 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Wed, 14 Jan 2026 09:25:23 -0600 Subject: [PATCH 48/53] PR feedback - fix docs and linter Also addressed discrepancy for query URL in state API docs --- .../compact-connect/docs/it_staff_onboarding_instructions.md | 4 ++-- .../python/data-events/handlers/military_audit_events.py | 2 -- backend/compact-connect/tests/smoke/README.md | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/backend/compact-connect/docs/it_staff_onboarding_instructions.md b/backend/compact-connect/docs/it_staff_onboarding_instructions.md index 184d99b40..c0fabb890 100644 --- a/backend/compact-connect/docs/it_staff_onboarding_instructions.md +++ b/backend/compact-connect/docs/it_staff_onboarding_instructions.md @@ -335,7 +335,7 @@ The access token you receive will be authorized for both operations. If this req 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//licenses/query` +**Endpoint**: `POST /v1/compacts//jurisdictions//providers/query` **Request Body**: ```json @@ -413,7 +413,7 @@ When querying for providers, results are paginated. To retrieve additional pages The get provider endpoint returns detailed information about a specific provider's privileges in your jurisdiction. -**Endpoint**: `GET /v1/compacts//jurisdictions//licenses/{providerId}` +**Endpoint**: `GET /v1/compacts//jurisdictions//providers/{providerId}` **Example Response**: ```json diff --git a/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py b/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py index c473012db..7c588b58c 100644 --- a/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py +++ b/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py @@ -1,5 +1,3 @@ -from uuid import UUID - from cc_common.config import config, logger from cc_common.data_model.schema.data_event.api import MilitaryAuditEventDetailSchema from cc_common.data_model.schema.military_affiliation.common import MilitaryAuditStatus diff --git a/backend/compact-connect/tests/smoke/README.md b/backend/compact-connect/tests/smoke/README.md index ce8c173a1..b017c8bc4 100644 --- a/backend/compact-connect/tests/smoke/README.md +++ b/backend/compact-connect/tests/smoke/README.md @@ -37,7 +37,7 @@ Ensure your AWS credentials are configured with appropriate permissions to: aws configure sso ``` Follow the prompts to set up your SSO profile using the values from your IAM identity center login - (see https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html#sso-configure-profile-token-auto-sso) + (see [AWS CLI SSO Configuration](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html#sso-configure-profile-token-auto-sso)) 2. Log in to AWS SSO: ```bash From 03bb544e7f3cd8e824f04851b128ade4d3d93ce9 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Wed, 14 Jan 2026 09:31:29 -0600 Subject: [PATCH 49/53] use variable name that doesn't shadow parameter --- .../python/data-events/handlers/military_audit_events.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py b/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py index 7c588b58c..4dd84a329 100644 --- a/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py +++ b/backend/compact-connect/lambdas/python/data-events/handlers/military_audit_events.py @@ -53,9 +53,9 @@ def military_audit_notification_listener(message: dict, tracker: NotificationTra if not provider_email: # this should not be possible, since only registered providers can upload military documentation # log the error and raise an exception - message = 'Provider registered email not found in system' - logger.error(message) - raise CCInternalException(message) + error_message = 'Provider registered email not found in system' + logger.error(error_message) + raise CCInternalException(error_message) # Check if we should send the notification (idempotency) if not tracker.should_send_provider_notification(): From 3b11ac1b01f24ff195c26304457ccf712884818e Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Wed, 14 Jan 2026 10:01:19 -0600 Subject: [PATCH 50/53] adjust sensitivity of provider search alarm --- .../stacks/search_persistent_stack/provider_search_domain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/compact-connect/stacks/search_persistent_stack/provider_search_domain.py b/backend/compact-connect/stacks/search_persistent_stack/provider_search_domain.py index 2e7ee6893..0afa708ba 100644 --- a/backend/compact-connect/stacks/search_persistent_stack/provider_search_domain.py +++ b/backend/compact-connect/stacks/search_persistent_stack/provider_search_domain.py @@ -521,7 +521,7 @@ def _add_capacity_alarms(self, alarm_topic: ITopic): period=Duration.minutes(5), statistic='Minimum', ), - evaluation_periods=1, # alert immediately when fewer than 10 searchable documents are detected + evaluation_periods=3, # set 3 periods to account for any temporary drops threshold=10, # set to 10 to account for any documents set by OpenSearch by default comparison_operator=ComparisonOperator.LESS_THAN_THRESHOLD, treat_missing_data=TreatMissingData.BREACHING, From 8c1fb67ecb0a5a1c47904ec03dbda65e7920e4b8 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Wed, 14 Jan 2026 10:17:25 -0600 Subject: [PATCH 51/53] Update OpenAPI spec to latest --- .../api-specification/latest-oas30.json | 260 +++++++++++++++--- 1 file changed, 225 insertions(+), 35 deletions(-) diff --git a/backend/compact-connect/docs/internal/api-specification/latest-oas30.json b/backend/compact-connect/docs/internal/api-specification/latest-oas30.json index 860f5240b..d140640c9 100644 --- a/backend/compact-connect/docs/internal/api-specification/latest-oas30.json +++ b/backend/compact-connect/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-01-14T16:16:32Z" }, "servers": [ { @@ -3225,6 +3225,144 @@ } } }, + "/v1/compacts/{compact}/providers/{providerId}/militaryAudit": { + "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": {} + } + } + }, + "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" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicenZnCDqNvEXOu2" + } + } + }, + "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/provider-users/me/jurisdiction/{jurisdiction}": { "options": { "parameters": [ @@ -6420,6 +6558,28 @@ }, "components": { "schemas": { + "SandboLicenZnCDqNvEXOu2": { + "required": [ + "militaryStatus" + ], + "type": "object", + "properties": { + "militaryStatusNote": { + "maxLength": 5000, + "type": "string", + "description": "Optional note from the admin (typically for declines)" + }, + "militaryStatus": { + "type": "string", + "description": "The audit result for the military documentation", + "enum": [ + "approved", + "declined" + ] + } + }, + "additionalProperties": false + }, "SandboLicenudbEF4n02FXU": { "required": [ "newEmailAddress" @@ -9146,6 +9306,42 @@ "coun" ] }, + "type": { + "type": "string", + "enum": [ + "provider" + ] + }, + "suffix": { + "maxLength": 100, + "minLength": 1, + "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" + }, + "familyName": { + "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" + }, "npi": { "pattern": "^[0-9]{10}$", "type": "string" @@ -9242,17 +9438,6 @@ ] } }, - "type": { - "type": "string", - "enum": [ - "provider" - ] - }, - "suffix": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, "currentHomeJurisdiction": { "type": "string", "description": "The current jurisdiction postal abbreviation if known.", @@ -9314,6 +9499,11 @@ "unknown" ] }, + "militaryStatusNote": { + "maxLength": 5000, + "type": "string", + "description": "Optional note about the military status (typically for declines)" + }, "licenses": { "type": "array", "items": { @@ -10204,10 +10394,15 @@ "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])$", + "militaryStatus": { "type": "string", - "format": "date" + "description": "Status of military affiliation on the provider record", + "enum": [ + "notApplicable", + "tentative", + "approved", + "declined" + ] }, "militaryAffiliations": { "type": "array", @@ -10292,10 +10487,6 @@ } } }, - "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": [ @@ -10303,27 +10494,11 @@ "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" @@ -12489,6 +12664,11 @@ "unknown" ] }, + "militaryStatusNote": { + "maxLength": 5000, + "type": "string", + "description": "Optional note about the military status (typically for declines)" + }, "ssnLastFour": { "pattern": "^[0-9]{4}$", "type": "string" @@ -12498,6 +12678,16 @@ "type": "string", "format": "date" }, + "militaryStatus": { + "type": "string", + "description": "Status of military affiliation on the provider record", + "enum": [ + "notApplicable", + "tentative", + "approved", + "declined" + ] + }, "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" From 61fda2e637a84011908596684d9ee07128fa0f6e Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Wed, 14 Jan 2026 11:03:56 -0600 Subject: [PATCH 52/53] PR feedback - fix comments --- .../lambdas/python/provider-data-v1/handlers/military_audit.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/military_audit.py b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/military_audit.py index ad7dddaa7..3000ca780 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/military_audit.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/military_audit.py @@ -18,8 +18,7 @@ def military_audit_handler(event: dict, context: LambdaContext) -> dict: Handle military audit requests from compact admins. This endpoint allows compact admins to approve or decline military documentation - uploaded by providers. The audit result is stored on both the provider record - and the latest military affiliation record. + uploaded by providers. The audit result is stored the provider record. :param event: API Gateway event :param context: Lambda context From a8b18267c07e853659423f84f4033d64ae7a194b Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Wed, 14 Jan 2026 11:13:51 -0600 Subject: [PATCH 53/53] PR feedback - grammar --- .../lambdas/python/provider-data-v1/handlers/military_audit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/military_audit.py b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/military_audit.py index 3000ca780..3b7193e1c 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/military_audit.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/military_audit.py @@ -18,7 +18,7 @@ def military_audit_handler(event: dict, context: LambdaContext) -> dict: Handle military audit requests from compact admins. This endpoint allows compact admins to approve or decline military documentation - uploaded by providers. The audit result is stored the provider record. + uploaded by providers. The audit result is stored in the provider record. :param event: API Gateway event :param context: Lambda context