diff --git a/.github/workflows/check-compact-connect-ui-app.yml b/.github/workflows/check-compact-connect-ui-app.yml index 6cb7e62e9..aaa478073 100644 --- a/.github/workflows/check-compact-connect-ui-app.yml +++ b/.github/workflows/check-compact-connect-ui-app.yml @@ -103,7 +103,7 @@ jobs: run: "pip install -r backend/compact-connect-ui-app/requirements-dev.txt" - name: Install all Python dependencies - run: "cd backend/compact-connect-ui-app; bin/sync_deps.sh" + run: "cd backend/compact-connect; pip install -U 'pip<25.3'; bin/sync_deps.sh" - name: Test backend run: "cd backend/compact-connect-ui-app; bin/run_tests.sh -l all -no" diff --git a/backend/compact-connect-ui-app/cdk.json b/backend/compact-connect-ui-app/cdk.json index aa3a63520..e272e7b0f 100644 --- a/backend/compact-connect-ui-app/cdk.json +++ b/backend/compact-connect-ui-app/cdk.json @@ -19,6 +19,32 @@ "project": "compact-connect", "service": "ui-app" }, + "jurisdictions": [ + "al", "ak", "az", "ar", "ca", "co", "ct", "de", "dc", "fl", + "ga", "hi", "id", "il", "in", "ia", "ks", "ky", "la", "me", + "md", "ma", "mi", "mn", "ms", "mo", "mt", "ne", "nv", "nh", + "nj", "nm", "ny", "nc", "nd", "oh", "ok", "or", "pa", "pr", + "ri", "sc", "sd", "tn", "tx", "ut", "vt", "va", "vi", "wa", + "wv", "wi", "wy" + ], + "license_types": { + "aslp": [ + {"name": "audiologist", "abbreviation": "aud"}, + {"name": "speech-language pathologist", "abbreviation": "slp"} + ], + "octp": [ + {"name": "occupational therapist", "abbreviation": "ot"}, + {"name": "occupational therapy assistant", "abbreviation": "ota"} + ], + "coun": [ + {"name": "licensed professional counselor", "abbreviation": "lpc"} + ] + }, + "compacts": [ + "aslp", + "octp", + "coun" + ], "@aws-cdk/aws-lambda:recognizeLayerVersion": true, "@aws-cdk/core:checkSecretUsage": true, "@aws-cdk/core:target-partitions": [ diff --git a/backend/compact-connect/bin/run_python_tests.py b/backend/compact-connect/bin/run_python_tests.py index 2fdca8f66..2f28afb39 100755 --- a/backend/compact-connect/bin/run_python_tests.py +++ b/backend/compact-connect/bin/run_python_tests.py @@ -30,10 +30,11 @@ 'lambdas/python/disaster-recovery', 'lambdas/python/migration', 'lambdas/python/provider-data-v1', - 'lambdas/python/purchases', 'lambdas/python/staff-user-pre-token', 'lambdas/python/staff-users', '.', # CDK tests + # Save Authorize.net tests for last, since special dependencies make that runtime different + 'lambdas/python/purchases', ) diff --git a/backend/compact-connect/docs/api-specification/latest-oas30.json b/backend/compact-connect/docs/api-specification/latest-oas30.json index f5e64b53a..b1996174f 100644 --- a/backend/compact-connect/docs/api-specification/latest-oas30.json +++ b/backend/compact-connect/docs/api-specification/latest-oas30.json @@ -10,61 +10,6 @@ } ], "paths": { - "/v1/public/jurisdictions/live": { - "get": { - "summary": "Get live jurisdictions", - "description": "Returns all jurisdictions that are live (enabled for operations) across all compacts or for a specific compact if the optional compact query parameter is provided.", - "parameters": [ - { - "name": "compact", - "in": "query", - "required": false, - "description": "Optional compact abbreviation to filter results. If not provided, returns data for all compacts. If an invalid compact is provided, returns a 400 error.", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "200 response - Returns a dictionary with compact abbreviations as keys and arrays of live jurisdiction postal abbreviations as values", - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - }, - "example": { - "aslp": ["co", "ne", "wy"], - "octp": ["ak", "ky"] - } - } - } - } - }, - "400": { - "description": "400 response - Invalid compact abbreviation provided", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Invalid request query param: invalid_compact" - } - } - } - } - } - } - } - } - }, "/v1/compacts/{compact}/jurisdictions/{jurisdiction}/licenses": { "post": { "parameters": [ 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 fc0040550..8abe15b1f 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": "2025-10-20T22:42:50Z" + "version": "2025-11-10T18:53:58Z" }, "servers": [ { @@ -10,61 +10,6 @@ } ], "paths": { - "/v1/public/jurisdictions/live": { - "get": { - "summary": "Get live jurisdictions", - "description": "Returns all jurisdictions that are live (enabled for operations) across all compacts or for a specific compact if the optional compact query parameter is provided.", - "parameters": [ - { - "name": "compact", - "in": "query", - "required": false, - "description": "Optional compact abbreviation to filter results. If not provided, returns data for all compacts. If an invalid compact is provided, returns a 400 error.", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "200 response - Returns a dictionary with compact abbreviations as keys and arrays of live jurisdiction postal abbreviations as values", - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - }, - "example": { - "aslp": ["co", "ne", "wy"], - "octp": ["ak", "ky"] - } - } - } - } - }, - "400": { - "description": "400 response - Invalid compact abbreviation provided", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Invalid request query param: invalid_compact" - } - } - } - } - } - } - } - } - }, "/v1/compacts/{compact}": { "get": { "parameters": [ @@ -91,7 +36,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenwpDMqHaHBxLL" + "$ref": "#/components/schemas/SandboLicenBxfAXNgCl0f1" } } } @@ -130,7 +75,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenqVKYQ6QCUUYV" + "$ref": "#/components/schemas/SandboLicenrzUhY3WW0Y7L" } } }, @@ -142,7 +87,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenaLbwv28ZqyH6" + "$ref": "#/components/schemas/SandboLicengeanQqZ1NjKt" } } } @@ -222,7 +167,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenFBKwuejTjcRR" + "$ref": "#/components/schemas/SandboLicenHcuQxywUvhOh" } } } @@ -259,7 +204,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicen74bwm5yy6E5W" + "$ref": "#/components/schemas/SandboLicenLPepHoMLi1aj" } } }, @@ -271,7 +216,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenT1eMkx5ptE5k" + "$ref": "#/components/schemas/SandboLicenou5OKotlJ6Ez" } } } @@ -343,7 +288,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenfM0YyXB9i4El" + "$ref": "#/components/schemas/SandboLicenHX8YNrOpPZbZ" } } } @@ -394,7 +339,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicene2a50HkoiVOX" + "$ref": "#/components/schemas/SandboLicenbGNGFVLD0EDE" } } } @@ -441,7 +386,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenXxQQC9wWjThg" + "$ref": "#/components/schemas/SandboLicenTi9BrhAERjPp" } } }, @@ -453,7 +398,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenaLbwv28ZqyH6" + "$ref": "#/components/schemas/SandboLicengeanQqZ1NjKt" } } } @@ -533,7 +478,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenYSm3tRorgR8v" + "$ref": "#/components/schemas/SandboLicenJIK60DVCsApb" } } } @@ -603,7 +548,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicen14sjN2dXfQbr" + "$ref": "#/components/schemas/SandboLicenYwiFMNF2Vu7Z" } } }, @@ -615,7 +560,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenaYqlozRvql5b" + "$ref": "#/components/schemas/SandboLiceno12FSfDWBzow" } } } @@ -666,7 +611,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicen6yHAPPYOdJ3t" + "$ref": "#/components/schemas/SandboLicenr5TVmpxKfPGq" } } } @@ -731,7 +676,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicen3QxrBb8JFDnT" + "$ref": "#/components/schemas/SandboLicenEAecRKtirVZk" } } }, @@ -743,7 +688,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenaLbwv28ZqyH6" + "$ref": "#/components/schemas/SandboLicengeanQqZ1NjKt" } } } @@ -845,7 +790,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenwjDvDRZGRaqT" + "$ref": "#/components/schemas/SandboLicen3zolh21hPpCk" } } }, @@ -857,7 +802,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenaLbwv28ZqyH6" + "$ref": "#/components/schemas/SandboLicengeanQqZ1NjKt" } } } @@ -903,7 +848,7 @@ ] } }, - "/v1/compacts/{compact}/providers/{providerId}/privileges/jurisdiction/{jurisdiction}/licenseType/{licenseType}/deactivate": { + "/v1/compacts/{compact}/providers/{providerId}/licenses/jurisdiction/{jurisdiction}/licenseType/{licenseType}/investigation": { "post": { "parameters": [ { @@ -951,7 +896,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenUTb3zNObrjW5" + "$ref": "#/components/schemas/SandboLicenZ1SdRwpLvl46" } } }, @@ -963,7 +908,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenaLbwv28ZqyH6" + "$ref": "#/components/schemas/SandboLicengeanQqZ1NjKt" } } } @@ -1009,8 +954,8 @@ ] } }, - "/v1/compacts/{compact}/providers/{providerId}/privileges/jurisdiction/{jurisdiction}/licenseType/{licenseType}/encumbrance": { - "post": { + "/v1/compacts/{compact}/providers/{providerId}/licenses/jurisdiction/{jurisdiction}/licenseType/{licenseType}/investigation/{investigationId}": { + "patch": { "parameters": [ { "name": "Authorization", @@ -1051,13 +996,21 @@ "schema": { "type": "string" } + }, + { + "name": "investigationId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } } ], "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenk1ql1I81jsMY" + "$ref": "#/components/schemas/SandboLicenvJ6cvnUE8Wwa" } } }, @@ -1069,7 +1022,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenaLbwv28ZqyH6" + "$ref": "#/components/schemas/SandboLicengeanQqZ1NjKt" } } } @@ -1115,8 +1068,8 @@ ] } }, - "/v1/compacts/{compact}/providers/{providerId}/privileges/jurisdiction/{jurisdiction}/licenseType/{licenseType}/encumbrance/{encumbranceId}": { - "patch": { + "/v1/compacts/{compact}/providers/{providerId}/privileges/jurisdiction/{jurisdiction}/licenseType/{licenseType}/deactivate": { + "post": { "parameters": [ { "name": "Authorization", @@ -1157,21 +1110,13 @@ "schema": { "type": "string" } - }, - { - "name": "encumbranceId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } } ], "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenGkLIz61xmgWo" + "$ref": "#/components/schemas/SandboLicenPAsEF1Ia4s7g" } } }, @@ -1183,7 +1128,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenaLbwv28ZqyH6" + "$ref": "#/components/schemas/SandboLicengeanQqZ1NjKt" } } } @@ -1229,8 +1174,8 @@ ] } }, - "/v1/compacts/{compact}/providers/{providerId}/privileges/jurisdiction/{jurisdiction}/licenseType/{licenseType}/history": { - "get": { + "/v1/compacts/{compact}/providers/{providerId}/privileges/jurisdiction/{jurisdiction}/licenseType/{licenseType}/encumbrance": { + "post": { "parameters": [ { "name": "Authorization", @@ -1273,135 +1218,23 @@ } } ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenZVm9nad5vQW4" - } + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicen6bEbVSG2KkBx" } } - } - }, - "security": [ - { - "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "aslp/readGeneral", - "octp/readGeneral", - "coun/readGeneral" - ] - } - ] - } - }, - "/v1/compacts/{compact}/providers/{providerId}/ssn": { - "get": { - "parameters": [ - { - "name": "Authorization", - "in": "header", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenBNvp4YDeh5rF" - } - } - } - } + "required": true }, - "security": [ - { - "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "aslp/readSSN", - "al/aslp.readSSN", - "ak/aslp.readSSN", - "ar/aslp.readSSN", - "co/aslp.readSSN", - "de/aslp.readSSN", - "ky/aslp.readSSN", - "la/aslp.readSSN", - "me/aslp.readSSN", - "md/aslp.readSSN", - "mn/aslp.readSSN", - "ms/aslp.readSSN", - "mo/aslp.readSSN", - "ne/aslp.readSSN", - "oh/aslp.readSSN", - "octp/readSSN", - "al/octp.readSSN", - "ar/octp.readSSN", - "ky/octp.readSSN", - "la/octp.readSSN", - "ms/octp.readSSN", - "ne/octp.readSSN", - "oh/octp.readSSN", - "coun/readSSN", - "al/coun.readSSN", - "ar/coun.readSSN", - "fl/coun.readSSN", - "ga/coun.readSSN", - "ky/coun.readSSN", - "ne/coun.readSSN", - "oh/coun.readSSN", - "ut/coun.readSSN" - ] - } - ] - } - }, - "/v1/compacts/{compact}/staff-users": { - "get": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], "responses": { "200": { "description": "200 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - } - }, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenjksXkBC6cpMT" + "$ref": "#/components/schemas/SandboLicengeanQqZ1NjKt" } } } @@ -1445,9 +1278,19 @@ ] } ] - }, - "post": { + } + }, + "/v1/compacts/{compact}/providers/{providerId}/privileges/jurisdiction/{jurisdiction}/licenseType/{licenseType}/encumbrance/{encumbranceId}": { + "patch": { "parameters": [ + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + } + }, { "name": "compact", "in": "path", @@ -1455,13 +1298,45 @@ "schema": { "type": "string" } + }, + { + "name": "providerId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "jurisdiction", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "licenseType", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "encumbranceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } } ], "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicen15BtNCg0ttcx" + "$ref": "#/components/schemas/SandboLicenVZHLGnXN7APB" } } }, @@ -1470,17 +1345,10 @@ "responses": { "200": { "description": "200 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - } - }, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenuLEzkOrUfJeb" + "$ref": "#/components/schemas/SandboLicengeanQqZ1NjKt" } } } @@ -1526,9 +1394,17 @@ ] } }, - "/v1/compacts/{compact}/staff-users/{userId}": { + "/v1/compacts/{compact}/providers/{providerId}/privileges/jurisdiction/{jurisdiction}/licenseType/{licenseType}/history": { "get": { "parameters": [ + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + } + }, { "name": "compact", "in": "path", @@ -1538,38 +1414,37 @@ } }, { - "name": "userId", + "name": "providerId", "in": "path", "required": true, "schema": { "type": "string" } - } - ], - "responses": { - "404": { - "description": "404 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenaLbwv28ZqyH6" - } - } + }, + { + "name": "jurisdiction", + "in": "path", + "required": true, + "schema": { + "type": "string" } }, + { + "name": "licenseType", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { "200": { "description": "200 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - } - }, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenuLEzkOrUfJeb" + "$ref": "#/components/schemas/SandboLicenobivPmYh5SvH" } } } @@ -1578,44 +1453,25 @@ "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" + "aslp/readGeneral", + "octp/readGeneral", + "coun/readGeneral" ] } ] - }, - "delete": { + } + }, + "/v1/compacts/{compact}/providers/{providerId}/privileges/jurisdiction/{jurisdiction}/licenseType/{licenseType}/investigation": { + "post": { "parameters": [ + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + } + }, { "name": "compact", "in": "path", @@ -1625,7 +1481,23 @@ } }, { - "name": "userId", + "name": "providerId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "jurisdiction", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "licenseType", "in": "path", "required": true, "schema": { @@ -1633,23 +1505,23 @@ } } ], - "responses": { - "404": { - "description": "404 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenaLbwv28ZqyH6" - } + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicenISmLYcqYrfLq" } } }, + "required": true + }, + "responses": { "200": { "description": "200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenaLbwv28ZqyH6" + "$ref": "#/components/schemas/SandboLicengeanQqZ1NjKt" } } } @@ -1693,9 +1565,19 @@ ] } ] - }, + } + }, + "/v1/compacts/{compact}/providers/{providerId}/privileges/jurisdiction/{jurisdiction}/licenseType/{licenseType}/investigation/{investigationId}": { "patch": { "parameters": [ + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + } + }, { "name": "compact", "in": "path", @@ -1705,7 +1587,31 @@ } }, { - "name": "userId", + "name": "providerId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "jurisdiction", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "licenseType", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "investigationId", "in": "path", "required": true, "schema": { @@ -1717,36 +1623,19 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicens8zWlw8e3VjA" + "$ref": "#/components/schemas/SandboLicenmwNUQ4l5yt99" } } }, "required": true }, "responses": { - "404": { - "description": "404 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenaLbwv28ZqyH6" - } - } - } - }, "200": { "description": "200 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - } - }, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenuLEzkOrUfJeb" + "$ref": "#/components/schemas/SandboLicengeanQqZ1NjKt" } } } @@ -1792,9 +1681,17 @@ ] } }, - "/v1/compacts/{compact}/staff-users/{userId}/reinvite": { - "post": { + "/v1/compacts/{compact}/providers/{providerId}/ssn": { + "get": { "parameters": [ + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + } + }, { "name": "compact", "in": "path", @@ -1804,7 +1701,7 @@ } }, { - "name": "userId", + "name": "providerId", "in": "path", "required": true, "schema": { @@ -1813,22 +1710,83 @@ } ], "responses": { - "404": { - "description": "404 response", + "200": { + "description": "200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenaLbwv28ZqyH6" + "$ref": "#/components/schemas/SandboLicen2x5yTuN7Vce5" } } } - }, + } + }, + "security": [ + { + "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ + "aslp/readSSN", + "al/aslp.readSSN", + "ak/aslp.readSSN", + "ar/aslp.readSSN", + "co/aslp.readSSN", + "de/aslp.readSSN", + "ky/aslp.readSSN", + "la/aslp.readSSN", + "me/aslp.readSSN", + "md/aslp.readSSN", + "mn/aslp.readSSN", + "ms/aslp.readSSN", + "mo/aslp.readSSN", + "ne/aslp.readSSN", + "oh/aslp.readSSN", + "octp/readSSN", + "al/octp.readSSN", + "ar/octp.readSSN", + "ky/octp.readSSN", + "la/octp.readSSN", + "ms/octp.readSSN", + "ne/octp.readSSN", + "oh/octp.readSSN", + "coun/readSSN", + "al/coun.readSSN", + "ar/coun.readSSN", + "fl/coun.readSSN", + "ga/coun.readSSN", + "ky/coun.readSSN", + "ne/coun.readSSN", + "oh/coun.readSSN", + "ut/coun.readSSN" + ] + } + ] + } + }, + "/v1/compacts/{compact}/staff-users": { + "get": { + "parameters": [ + { + "name": "compact", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { "200": { "description": "200 response", + "headers": { + "Access-Control-Allow-Origin": { + "schema": { + "type": "string" + } + } + }, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenaLbwv28ZqyH6" + "$ref": "#/components/schemas/SandboLicenLLx7v2Sq2Nku" } } } @@ -1872,13 +1830,11 @@ ] } ] - } - }, - "/v1/flags/{flagId}/check": { + }, "post": { "parameters": [ { - "name": "flagId", + "name": "compact", "in": "path", "required": true, "schema": { @@ -1890,7 +1846,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenS3OpuzvlfqIR" + "$ref": "#/components/schemas/SandboLicenDb8Dl04rAoPD" } } }, @@ -1899,103 +1855,106 @@ "responses": { "200": { "description": "200 response", - "content": { - "application/json": { + "headers": { + "Access-Control-Allow-Origin": { "schema": { - "$ref": "#/components/schemas/SandboLiceno4zXe3APQ07E" + "type": "string" } } - } - } - } - } - }, - "/v1/provider-users/initiateRecovery": { - "post": { - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicen2TYOnVghC533" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "200 response", + }, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenaLbwv28ZqyH6" + "$ref": "#/components/schemas/SandboLicen2VPoPHwDEcqq" } } } } - } + }, + "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": { + "/v1/compacts/{compact}/staff-users/{userId}": { "get": { "parameters": [ { - "name": "Authorization", - "in": "header", + "name": "compact", + "in": "path", "required": true, "schema": { "type": "string" } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicen6yHAPPYOdJ3t" - } - } - } - } - }, - "security": [ - { - "SandboxAPIStackLicenseApiProviderUsersPoolAuthorizerEB7523BA": [] - } - ] - } - }, - "/v1/provider-users/me/email": { - "patch": { - "parameters": [ + }, { - "name": "Authorization", - "in": "header", + "name": "userId", + "in": "path", "required": true, "schema": { "type": "string" } } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLiceno1FF4B6IPNWg" + "responses": { + "404": { + "description": "404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicengeanQqZ1NjKt" + } } } }, - "required": true - }, - "responses": { "200": { "description": "200 response", + "headers": { + "Access-Control-Allow-Origin": { + "schema": { + "type": "string" + } + } + }, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenaLbwv28ZqyH6" + "$ref": "#/components/schemas/SandboLicen2VPoPHwDEcqq" } } } @@ -2003,40 +1962,79 @@ }, "security": [ { - "SandboxAPIStackLicenseApiProviderUsersPoolAuthorizerEB7523BA": [] + "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/email/verify": { - "post": { + }, + "delete": { "parameters": [ { - "name": "Authorization", - "in": "header", + "name": "compact", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "userId", + "in": "path", "required": true, "schema": { "type": "string" } } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicen34TBFk9LdAJJ" + "responses": { + "404": { + "description": "404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicengeanQqZ1NjKt" + } } } }, - "required": true - }, - "responses": { "200": { "description": "200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenaLbwv28ZqyH6" + "$ref": "#/components/schemas/SandboLicengeanQqZ1NjKt" } } } @@ -2044,17 +2042,56 @@ }, "security": [ { - "SandboxAPIStackLicenseApiProviderUsersPoolAuthorizerEB7523BA": [] + "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/home-jurisdiction": { - "put": { + }, + "patch": { "parameters": [ { - "name": "Authorization", - "in": "header", + "name": "compact", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "userId", + "in": "path", "required": true, "schema": { "type": "string" @@ -2065,19 +2102,36 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenaFsmBhKzUkNI" + "$ref": "#/components/schemas/SandboLicenC1PiOZAh5Usx" } } }, "required": true }, "responses": { + "404": { + "description": "404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicengeanQqZ1NjKt" + } + } + } + }, "200": { "description": "200 response", + "headers": { + "Access-Control-Allow-Origin": { + "schema": { + "type": "string" + } + } + }, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenaLbwv28ZqyH6" + "$ref": "#/components/schemas/SandboLicen2VPoPHwDEcqq" } } } @@ -2085,24 +2139,49 @@ }, "security": [ { - "SandboxAPIStackLicenseApiProviderUsersPoolAuthorizerEB7523BA": [] - } - ] - } - }, - "/v1/provider-users/me/jurisdiction/{jurisdiction}/licenseType/{licenseType}/history": { - "get": { + "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ + "aslp/admin", + "al/aslp.admin", + "ak/aslp.admin", + "ar/aslp.admin", + "co/aslp.admin", + "de/aslp.admin", + "ky/aslp.admin", + "la/aslp.admin", + "me/aslp.admin", + "md/aslp.admin", + "mn/aslp.admin", + "ms/aslp.admin", + "mo/aslp.admin", + "ne/aslp.admin", + "oh/aslp.admin", + "octp/admin", + "al/octp.admin", + "ar/octp.admin", + "ky/octp.admin", + "la/octp.admin", + "ms/octp.admin", + "ne/octp.admin", + "oh/octp.admin", + "coun/admin", + "al/coun.admin", + "ar/coun.admin", + "fl/coun.admin", + "ga/coun.admin", + "ky/coun.admin", + "ne/coun.admin", + "oh/coun.admin", + "ut/coun.admin" + ] + } + ] + } + }, + "/v1/compacts/{compact}/staff-users/{userId}/reinvite": { + "post": { "parameters": [ { - "name": "Authorization", - "in": "header", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", + "name": "compact", "in": "path", "required": true, "schema": { @@ -2110,7 +2189,7 @@ } }, { - "name": "licenseType", + "name": "userId", "in": "path", "required": true, "schema": { @@ -2119,53 +2198,22 @@ } ], "responses": { - "200": { - "description": "200 response", + "404": { + "description": "404 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenZVm9nad5vQW4" + "$ref": "#/components/schemas/SandboLicengeanQqZ1NjKt" } } } - } - }, - "security": [ - { - "SandboxAPIStackLicenseApiProviderUsersPoolAuthorizerEB7523BA": [] - } - ] - } - }, - "/v1/provider-users/me/military-affiliation": { - "post": { - "parameters": [ - { - "name": "Authorization", - "in": "header", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenOJHHzc75m2Dh" - } - } }, - "required": true - }, - "responses": { "200": { "description": "200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenbmJo5pJTGNyk" + "$ref": "#/components/schemas/SandboLicengeanQqZ1NjKt" } } } @@ -2173,15 +2221,50 @@ }, "security": [ { - "SandboxAPIStackLicenseApiProviderUsersPoolAuthorizerEB7523BA": [] + "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" + ] } ] - }, - "patch": { + } + }, + "/v1/flags/{flagId}/check": { + "post": { "parameters": [ { - "name": "Authorization", - "in": "header", + "name": "flagId", + "in": "path", "required": true, "schema": { "type": "string" @@ -2192,7 +2275,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenDDeRKnGCOQFv" + "$ref": "#/components/schemas/SandboLicenHVkU4oNl0nsF" } } }, @@ -2204,26 +2287,21 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenaLbwv28ZqyH6" + "$ref": "#/components/schemas/SandboLicen0ONaFbyAwP9V" } } } } - }, - "security": [ - { - "SandboxAPIStackLicenseApiProviderUsersPoolAuthorizerEB7523BA": [] - } - ] + } } }, - "/v1/provider-users/registration": { + "/v1/provider-users/initiateRecovery": { "post": { "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenUascxyoiRHZo" + "$ref": "#/components/schemas/SandboLicenpPZOnBEPSaJL" } } }, @@ -2235,7 +2313,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenaLbwv28ZqyH6" + "$ref": "#/components/schemas/SandboLicengeanQqZ1NjKt" } } } @@ -2243,64 +2321,84 @@ } } }, - "/v1/provider-users/verifyRecovery": { - "post": { - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenbvbpTeGqjAcZ" - } + "/v1/provider-users/me": { + "get": { + "parameters": [ + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" } - }, - "required": true - }, + } + ], "responses": { "200": { "description": "200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenaLbwv28ZqyH6" + "$ref": "#/components/schemas/SandboLicenr5TVmpxKfPGq" } } } } - } + }, + "security": [ + { + "SandboxAPIStackLicenseApiProviderUsersPoolAuthorizerEB7523BA": [] + } + ] } }, - "/v1/public/compacts/{compact}/jurisdictions": { - "get": { + "/v1/provider-users/me/email": { + "patch": { "parameters": [ { - "name": "compact", - "in": "path", + "name": "Authorization", + "in": "header", "required": true, "schema": { "type": "string" } } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicenaSmGZue6afyn" + } + } + }, + "required": true + }, "responses": { "200": { "description": "200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenfM0YyXB9i4El" + "$ref": "#/components/schemas/SandboLicengeanQqZ1NjKt" } } } } - } + }, + "security": [ + { + "SandboxAPIStackLicenseApiProviderUsersPoolAuthorizerEB7523BA": [] + } + ] } }, - "/v1/public/compacts/{compact}/providers/query": { + "/v1/provider-users/me/email/verify": { "post": { "parameters": [ { - "name": "compact", - "in": "path", + "name": "Authorization", + "in": "header", "required": true, "schema": { "type": "string" @@ -2311,7 +2409,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicen14sjN2dXfQbr" + "$ref": "#/components/schemas/SandboLicenbcsmAhiZJZ0Q" } } }, @@ -2323,62 +2421,66 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenxSf6anmMiZUl" + "$ref": "#/components/schemas/SandboLicengeanQqZ1NjKt" } } } } - } + }, + "security": [ + { + "SandboxAPIStackLicenseApiProviderUsersPoolAuthorizerEB7523BA": [] + } + ] } }, - "/v1/public/compacts/{compact}/providers/{providerId}": { - "get": { + "/v1/provider-users/me/home-jurisdiction": { + "put": { "parameters": [ { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", + "name": "Authorization", + "in": "header", "required": true, "schema": { "type": "string" } } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicen8QcYRI7AzDyX" + } + } + }, + "required": true + }, "responses": { "200": { "description": "200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenkPqluVn6dJHe" + "$ref": "#/components/schemas/SandboLicengeanQqZ1NjKt" } } } } - } + }, + "security": [ + { + "SandboxAPIStackLicenseApiProviderUsersPoolAuthorizerEB7523BA": [] + } + ] } }, - "/v1/public/compacts/{compact}/providers/{providerId}/jurisdiction/{jurisdiction}/licenseType/{licenseType}/history": { + "/v1/provider-users/me/jurisdiction/{jurisdiction}/licenseType/{licenseType}/history": { "get": { "parameters": [ { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", + "name": "Authorization", + "in": "header", "required": true, "schema": { "type": "string" @@ -2407,15 +2509,20 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenZVm9nad5vQW4" + "$ref": "#/components/schemas/SandboLicenobivPmYh5SvH" } } } } - } - } - }, - "/v1/purchases/privileges": { + }, + "security": [ + { + "SandboxAPIStackLicenseApiProviderUsersPoolAuthorizerEB7523BA": [] + } + ] + } + }, + "/v1/provider-users/me/military-affiliation": { "post": { "parameters": [ { @@ -2431,7 +2538,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenLhkhDxX4fiIg" + "$ref": "#/components/schemas/SandboLicenJZNKekBSUCl5" } } }, @@ -2443,7 +2550,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenrZuAY9tZ4fCA" + "$ref": "#/components/schemas/SandboLicenUzZJQKKk5goL" } } } @@ -2454,10 +2561,8 @@ "SandboxAPIStackLicenseApiProviderUsersPoolAuthorizerEB7523BA": [] } ] - } - }, - "/v1/purchases/privileges/options": { - "get": { + }, + "patch": { "parameters": [ { "name": "Authorization", @@ -2468,13 +2573,23 @@ } } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicendERazg9NeEsA" + } + } + }, + "required": true + }, "responses": { "200": { "description": "200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenBqFE7Lkpte9Z" + "$ref": "#/components/schemas/SandboLicengeanQqZ1NjKt" } } } @@ -2487,348 +2602,487 @@ ] } }, - "/v1/staff-users/me": { - "get": { + "/v1/provider-users/registration": { + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicenwoqrSnm0rsGM" + } + } + }, + "required": true + }, "responses": { - "404": { - "description": "404 response", + "200": { + "description": "200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenaLbwv28ZqyH6" + "$ref": "#/components/schemas/SandboLicengeanQqZ1NjKt" } } } + } + } + } + }, + "/v1/provider-users/verifyRecovery": { + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicenirtyanNQfXka" + } + } }, + "required": true + }, + "responses": { "200": { "description": "200 response", - "headers": { - "Access-Control-Allow-Origin": { + "content": { + "application/json": { "schema": { - "type": "string" + "$ref": "#/components/schemas/SandboLicengeanQqZ1NjKt" } } - }, + } + } + } + } + }, + "/v1/public/compacts/{compact}/jurisdictions": { + "get": { + "parameters": [ + { + "name": "compact", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenuLEzkOrUfJeb" + "$ref": "#/components/schemas/SandboLicenHX8YNrOpPZbZ" } } } } - }, - "security": [ + } + } + }, + "/v1/public/compacts/{compact}/providers/query": { + "post": { + "parameters": [ { - "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "profile" - ] + "name": "compact", + "in": "path", + "required": true, + "schema": { + "type": "string" + } } - ] - }, - "patch": { + ], "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenQrINPgMnQioZ" + "$ref": "#/components/schemas/SandboLicenYwiFMNF2Vu7Z" } } }, "required": true }, "responses": { - "404": { - "description": "404 response", + "200": { + "description": "200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenaLbwv28ZqyH6" + "$ref": "#/components/schemas/SandboLicenBxXRobaYJSR9" } } } + } + } + } + }, + "/v1/public/compacts/{compact}/providers/{providerId}": { + "get": { + "parameters": [ + { + "name": "compact", + "in": "path", + "required": true, + "schema": { + "type": "string" + } }, + { + "name": "providerId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { "200": { "description": "200 response", - "headers": { - "Access-Control-Allow-Origin": { + "content": { + "application/json": { "schema": { - "type": "string" + "$ref": "#/components/schemas/SandboLicendF8CsBCXAU1x" } } - }, + } + } + } + } + }, + "/v1/public/compacts/{compact}/providers/{providerId}/jurisdiction/{jurisdiction}/licenseType/{licenseType}/history": { + "get": { + "parameters": [ + { + "name": "compact", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "providerId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "jurisdiction", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "licenseType", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenuLEzkOrUfJeb" + "$ref": "#/components/schemas/SandboLicenobivPmYh5SvH" } } } } - }, - "security": [ + } + } + }, + "/v1/public/jurisdictions/live": { + "get": { + "parameters": [ { - "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "profile" - ] + "name": "compact", + "in": "query", + "schema": { + "type": "string" + } } - ] - } - } - }, - "components": { - "schemas": { - "SandboLicenBNvp4YDeh5rF": { - "required": [ - "ssn" ], - "type": "object", - "properties": { - "ssn": { - "pattern": "^[0-9]{3}-[0-9]{2}-[0-9]{4}$", - "type": "string", - "description": "The provider's social security number" + "responses": { + "200": { + "description": "200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicenZdwYMaXuEYs6" + } + } + } } } - }, - "SandboLicenXxQQC9wWjThg": { - "required": [ - "jurisdictionAdverseActionsNotificationEmails", - "jurisdictionOperationsTeamEmails", - "jurisdictionSummaryReportNotificationEmails", - "jurisprudenceRequirements", - "licenseeRegistrationEnabled", - "privilegeFees" + } + }, + "/v1/purchases/privileges": { + "post": { + "parameters": [ + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + } + } ], - "type": "object", - "properties": { - "privilegeFees": { - "type": "array", - "description": "The fees for the privileges by license type", - "items": { - "required": [ - "amount", - "licenseTypeAbbreviation" - ], - "type": "object", - "properties": { - "amount": { - "minimum": 0, - "type": "number" - }, - "militaryRate": { - "description": "Optional military rate for the privilege fee.", - "oneOf": [ - { - "minimum": 0, - "type": "number" - }, - null - ] - }, - "licenseTypeAbbreviation": { - "type": "string", - "enum": [ - "aud", - "slp", - "ot", - "ota", - "lpc" - ] - } - }, - "additionalProperties": false + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicenBWZgN83iyevY" + } } }, - "jurisdictionAdverseActionsNotificationEmails": { - "maxItems": 10, - "minItems": 1, - "uniqueItems": true, - "type": "array", - "description": "List of email addresses for adverse actions notifications", - "items": { - "type": "string", - "format": "email" + "required": true + }, + "responses": { + "200": { + "description": "200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLiceng72nqBB23bmL" + } + } } - }, - "jurisdictionOperationsTeamEmails": { - "maxItems": 10, - "minItems": 1, - "uniqueItems": true, - "type": "array", - "description": "List of email addresses for operations team notifications", - "items": { - "type": "string", - "format": "email" + } + }, + "security": [ + { + "SandboxAPIStackLicenseApiProviderUsersPoolAuthorizerEB7523BA": [] + } + ] + } + }, + "/v1/purchases/privileges/options": { + "get": { + "parameters": [ + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicenI2vqm7fRdSBi" + } + } + } + } + }, + "security": [ + { + "SandboxAPIStackLicenseApiProviderUsersPoolAuthorizerEB7523BA": [] + } + ] + } + }, + "/v1/staff-users/me": { + "get": { + "responses": { + "404": { + "description": "404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicengeanQqZ1NjKt" + } + } } }, - "jurisprudenceRequirements": { - "required": [ - "required" - ], - "type": "object", - "properties": { - "linkToDocumentation": { - "description": "Optional link to jurisprudence documentation", - "oneOf": [ - { - "type": "string" - }, - null - ] - }, - "required": { - "type": "boolean", - "description": "Whether jurisprudence requirements exist" + "200": { + "description": "200 response", + "headers": { + "Access-Control-Allow-Origin": { + "schema": { + "type": "string" + } } }, - "additionalProperties": false + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicen2VPoPHwDEcqq" + } + } + } + } + }, + "security": [ + { + "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ + "profile" + ] + } + ] + }, + "patch": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicenI4CqYMff5SWl" + } + } }, - "licenseeRegistrationEnabled": { - "type": "boolean", - "description": "Denotes whether licensee registration is enabled" + "required": true + }, + "responses": { + "404": { + "description": "404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicengeanQqZ1NjKt" + } + } + } }, - "jurisdictionSummaryReportNotificationEmails": { - "maxItems": 10, - "minItems": 1, - "uniqueItems": true, - "type": "array", - "description": "List of email addresses for summary report notifications", - "items": { - "type": "string", - "format": "email" + "200": { + "description": "200 response", + "headers": { + "Access-Control-Allow-Origin": { + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicen2VPoPHwDEcqq" + } + } } } }, + "security": [ + { + "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ + "profile" + ] + } + ] + } + } + }, + "components": { + "schemas": { + "SandboLicen2x5yTuN7Vce5": { + "required": [ + "ssn" + ], + "type": "object", + "properties": { + "ssn": { + "pattern": "^[0-9]{3}-[0-9]{2}-[0-9]{4}$", + "type": "string", + "description": "The provider's social security number" + } + } + }, + "SandboLicenbcsmAhiZJZ0Q": { + "required": [ + "verificationCode" + ], + "type": "object", + "properties": { + "verificationCode": { + "pattern": "^[0-9]{4}$", + "type": "string", + "description": "4-digit verification code" + } + }, "additionalProperties": false }, - "SandboLicenUascxyoiRHZo": { + "SandboLicenvJ6cvnUE8Wwa": { "required": [ - "compact", - "dob", - "email", - "familyName", - "givenName", - "jurisdiction", - "licenseType", - "partialSocial", - "token" + "action" ], "type": "object", "properties": { - "licenseType": { - "maxLength": 500, + "action": { "type": "string", - "description": "Type of license", "enum": [ - "audiologist", - "speech-language pathologist", - "occupational therapist", - "occupational therapy assistant", - "licensed professional counselor" + "close" ] }, - "compact": { - "maxLength": 100, - "type": "string", - "description": "Compact name" - }, - "dob": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "description": "Date of birth in YYYY-MM-DD format" - }, - "givenName": { - "maxLength": 200, - "type": "string", - "description": "Provider's given name" - }, - "familyName": { - "maxLength": 200, - "type": "string", - "description": "Provider's family name" - }, - "jurisdiction": { - "maxLength": 2, - "minLength": 2, - "type": "string", - "description": "Two-letter jurisdiction code", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "partialSocial": { - "maxLength": 4, - "minLength": 4, - "type": "string", - "description": "Last 4 digits of SSN" - }, - "email": { - "maxLength": 100, - "minLength": 5, - "type": "string", - "description": "Provider's email address", - "format": "email" - }, - "token": { - "type": "string", - "description": "ReCAPTCHA token" + "encumbrance": { + "required": [ + "encumbranceEffectiveDate", + "encumbranceType" + ], + "type": "object", + "properties": { + "encumbranceEffectiveDate": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "description": "The effective date of the encumbrance", + "format": "date" + }, + "clinicalPrivilegeActionCategories": { + "type": "array", + "description": "The categories of clinical privilege action", + "items": { + "type": "string" + } + }, + "encumbranceType": { + "type": "string", + "description": "The type of encumbrance", + "enum": [ + "fine", + "reprimand", + "required supervision", + "completion of continuing education", + "public reprimand", + "probation", + "injunctive action", + "suspension", + "revocation", + "denial", + "surrender of license", + "modification of previous action-extension", + "modification of previous action-reduction", + "other monitoring", + "other adjudicated action not listed" + ] + }, + "clinicalPrivilegeActionCategory": { + "type": "string", + "description": "(Deprecated) The category of clinical privilege action. Use clinicalPrivilegeActionCategories instead." + } + }, + "additionalProperties": false, + "description": "Encumbrance data to create" } } }, - "SandboLicenYSm3tRorgR8v": { + "SandboLicenISmLYcqYrfLq": { + "type": "object", + "properties": {} + }, + "SandboLicenJIK60DVCsApb": { "required": [ "upload" ], @@ -2854,749 +3108,400 @@ } } }, - "SandboLicenaYqlozRvql5b": { + "SandboLicen6bEbVSG2KkBx": { "required": [ - "pagination", - "providers" + "encumbranceEffectiveDate", + "encumbranceType" ], "type": "object", "properties": { - "pagination": { - "type": "object", - "properties": { - "prevLastKey": { - "maxLength": 1024, - "minLength": 1, - "type": "object" - }, - "lastKey": { - "maxLength": 1024, - "minLength": 1, - "type": "object" - }, - "pageSize": { - "maximum": 100, - "minimum": 5, - "type": "integer" - } - } - }, - "sorting": { - "required": [ - "key" - ], - "type": "object", - "properties": { - "key": { - "type": "string", - "description": "The key to sort results by", - "enum": [ - "dateOfUpdate", - "familyName" - ] - }, - "direction": { - "type": "string", - "description": "Direction to sort results by", - "enum": [ - "ascending", - "descending" - ] - } - }, - "description": "How to sort results" + "encumbranceEffectiveDate": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "description": "The effective date of the encumbrance", + "format": "date" }, - "providers": { - "maxLength": 100, + "clinicalPrivilegeActionCategories": { "type": "array", + "description": "The categories of clinical privilege action", "items": { - "required": [ - "birthMonthDay", - "compact", - "compactEligibility", - "dateOfExpiration", - "dateOfUpdate", - "familyName", - "givenName", - "jurisdictionUploadedCompactEligibility", - "jurisdictionUploadedLicenseStatus", - "licenseJurisdiction", - "licenseStatus", - "privilegeJurisdictions", - "providerId", - "type" - ], - "type": "object", - "properties": { - "licenseJurisdiction": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "compact": { - "type": "string", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, - "givenName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "compactEligibility": { - "type": "string", - "enum": [ - "eligible", - "ineligible" - ] - }, - "jurisdictionUploadedCompactEligibility": { - "type": "string", - "enum": [ - "eligible", - "ineligible" - ] - }, - "dateOfBirth": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "jurisdictionUploadedLicenseStatus": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - }, - "privilegeJurisdictions": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - } - }, - "type": { - "type": "string", - "enum": [ - "provider" - ] - }, - "suffix": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "currentHomeJurisdiction": { - "type": "string", - "description": "The current jurisdiction postal abbreviation if known.", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy", - "other", - "unknown" - ] - }, - "ssnLastFour": { - "pattern": "^[0-9]{4}$", - "type": "string" - }, - "dateOfExpiration": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, - "licenseStatus": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - }, - "familyName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "middleName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "birthMonthDay": { - "pattern": "^[01]{1}[0-9]{1}-[0-3]{1}[0-9]{1}$", - "type": "string", - "format": "date" - }, - "compactConnectRegisteredEmailAddress": { - "maxLength": 100, - "minLength": 5, - "type": "string", - "format": "email" - }, - "dateOfUpdate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - } - } + "type": "string" } + }, + "encumbranceType": { + "type": "string", + "description": "The type of encumbrance", + "enum": [ + "fine", + "reprimand", + "required supervision", + "completion of continuing education", + "public reprimand", + "probation", + "injunctive action", + "suspension", + "revocation", + "denial", + "surrender of license", + "modification of previous action-extension", + "modification of previous action-reduction", + "other monitoring", + "other adjudicated action not listed" + ] + }, + "clinicalPrivilegeActionCategory": { + "type": "string", + "description": "(Deprecated) The category of clinical privilege action. Use clinicalPrivilegeActionCategories instead." } - } + }, + "additionalProperties": false, + "description": "Encumbrance data to create" }, - "SandboLicenwpDMqHaHBxLL": { + "SandboLicenVZHLGnXN7APB": { "required": [ - "compactAbbr", - "compactAdverseActionsNotificationEmails", - "compactCommissionFee", - "compactName", - "compactOperationsTeamEmails", - "compactSummaryReportNotificationEmails", - "configuredStates", - "licenseeRegistrationEnabled" + "effectiveLiftDate" ], "type": "object", "properties": { - "configuredStates": { - "type": "array", - "description": "List of states that have submitted configurations and their live status", - "items": { - "required": [ - "isLive", - "postalAbbreviation" - ], - "type": "object", - "properties": { - "postalAbbreviation": { - "type": "string", - "description": "The postal abbreviation of the jurisdiction", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] + "effectiveLiftDate": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "description": "The effective date when the encumbrance will be lifted", + "format": "date" + } + }, + "additionalProperties": false + }, + "SandboLicenirtyanNQfXka": { + "required": [ + "compact", + "providerId", + "recaptchaToken", + "recoveryToken" + ], + "type": "object", + "properties": { + "compact": { + "type": "string", + "description": "Compact abbreviation", + "enum": [ + "aslp", + "octp", + "coun" + ] + }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string", + "description": "Provider UUID" + }, + "recaptchaToken": { + "minLength": 1, + "type": "string", + "description": "ReCAPTCHA token for verification" + }, + "recoveryToken": { + "maxLength": 256, + "minLength": 1, + "type": "string", + "description": "Recovery token from the email link" + } + }, + "additionalProperties": false + }, + "SandboLicen2VPoPHwDEcqq": { + "required": [ + "attributes", + "permissions", + "status", + "userId" + ], + "type": "object", + "properties": { + "permissions": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "actions": { + "type": "object", + "properties": { + "readPrivate": { + "type": "boolean" + }, + "admin": { + "type": "boolean" + }, + "readSSN": { + "type": "boolean" + } + } }, - "isLive": { - "type": "boolean", - "description": "Whether the state is live and available for registrations." + "jurisdictions": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "actions": { + "type": "object", + "properties": { + "readPrivate": { + "type": "boolean" + }, + "admin": { + "type": "boolean" + }, + "write": { + "type": "boolean" + }, + "readSSN": { + "type": "boolean" + } + }, + "additionalProperties": false + } + } + } } - } + }, + "additionalProperties": false } }, - "compactCommissionFee": { + "attributes": { "required": [ - "feeAmount", - "feeType" + "email", + "familyName", + "givenName" ], "type": "object", "properties": { - "feeAmount": { - "type": "number" + "givenName": { + "maxLength": 100, + "minLength": 1, + "type": "string" }, - "feeType": { - "type": "string", - "enum": [ - "FLAT_RATE" - ] + "familyName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "email": { + "maxLength": 100, + "minLength": 5, + "type": "string" } - } + }, + "additionalProperties": false }, - "compactSummaryReportNotificationEmails": { - "type": "array", - "description": "List of email addresses for summary report notifications", - "items": { - "type": "string", - "format": "email" - } + "userId": { + "type": "string" }, - "compactAdverseActionsNotificationEmails": { + "status": { + "type": "string", + "enum": [ + "active", + "inactive" + ] + } + }, + "additionalProperties": false + }, + "SandboLicenUzZJQKKk5goL": { + "required": [ + "affiliationType", + "dateOfUpdate", + "dateOfUpload", + "documentUploadFields", + "status" + ], + "type": "object", + "properties": { + "dateOfUpload": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "description": "The date the document was uploaded", + "format": "date" + }, + "affiliationType": { + "type": "string", + "description": "The type of military affiliation", + "enum": [ + "militaryMember", + "militaryMemberSpouse" + ] + }, + "fileNames": { "type": "array", - "description": "List of email addresses for adverse actions notifications", + "description": "List of military affiliation file names", "items": { "type": "string", - "format": "email" + "description": "The name of the file being uploaded" } }, - "licenseeRegistrationEnabled": { - "type": "boolean", - "description": "Denotes whether licensee registration is enabled" + "dateOfUpdate": { + "type": "string", + "description": "The date the document was last updated", + "format": "date-time" }, - "compactAbbr": { + "status": { "type": "string", - "description": "The abbreviation of the compact" + "description": "The status of the military affiliation" }, - "transactionFeeConfiguration": { - "type": "object", - "properties": { - "licenseeCharges": { - "required": [ - "active", - "chargeAmount", - "chargeType" - ], - "type": "object", - "properties": { - "chargeType": { - "type": "string", - "description": "The type of transaction fee charge", - "enum": [ - "FLAT_FEE_PER_PRIVILEGE" - ] - }, - "active": { - "type": "boolean", - "description": "Whether the compact is charging licensees transaction fees" - }, - "chargeAmount": { - "type": "number", - "description": "The amount to charge per privilege purchased" - } - } - } - } - }, - "compactName": { - "type": "string", - "description": "The full name of the compact" - }, - "compactOperationsTeamEmails": { + "documentUploadFields": { "type": "array", - "description": "List of email addresses for operations team notifications", + "description": "The fields used to upload documents", "items": { - "type": "string", - "format": "email" + "type": "object", + "properties": { + "fields": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "The form fields used to upload the document" + }, + "url": { + "type": "string", + "description": "The url to upload the document to" + } + }, + "description": "The fields used to upload a specific document" } } } }, - "SandboLicenqVKYQ6QCUUYV": { + "SandboLicenYwiFMNF2Vu7Z": { "required": [ - "compactAdverseActionsNotificationEmails", - "compactCommissionFee", - "compactOperationsTeamEmails", - "compactSummaryReportNotificationEmails", - "configuredStates", - "licenseeRegistrationEnabled" + "query" ], "type": "object", "properties": { - "configuredStates": { - "type": "array", - "description": "List of states that have submitted configurations and their live status", - "items": { - "required": [ - "isLive", - "postalAbbreviation" - ], - "type": "object", - "properties": { - "postalAbbreviation": { - "type": "string", - "description": "The postal abbreviation of the jurisdiction", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "isLive": { - "type": "boolean", - "description": "Whether the state is live and available for registrations." - } + "pagination": { + "type": "object", + "properties": { + "lastKey": { + "maxLength": 1024, + "minLength": 1, + "type": "string" }, - "additionalProperties": false - } + "pageSize": { + "maximum": 100, + "minimum": 5, + "type": "integer" + } + }, + "additionalProperties": false }, - "compactCommissionFee": { - "required": [ - "feeAmount", - "feeType" - ], + "query": { "type": "object", "properties": { - "feeAmount": { - "minimum": 0, - "type": "number" + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string", + "description": "Internal UUID for the provider" }, - "feeType": { + "jurisdiction": { "type": "string", + "description": "Filter for providers with privilege/license in a jurisdiction", "enum": [ - "FLAT_RATE" + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" ] + }, + "givenName": { + "maxLength": 100, + "type": "string", + "description": "Filter for providers with a given name (familyName is required if givenName is provided)" + }, + "familyName": { + "maxLength": 100, + "type": "string", + "description": "Filter for providers with a family name" } }, - "additionalProperties": false - }, - "compactSummaryReportNotificationEmails": { - "maxItems": 10, - "minItems": 1, - "uniqueItems": true, - "type": "array", - "description": "List of email addresses for summary report notifications", - "items": { - "type": "string", - "format": "email" - } - }, - "compactAdverseActionsNotificationEmails": { - "maxItems": 10, - "minItems": 1, - "uniqueItems": true, - "type": "array", - "description": "List of email addresses for adverse actions notifications", - "items": { - "type": "string", - "format": "email" - } - }, - "licenseeRegistrationEnabled": { - "type": "boolean", - "description": "Denotes whether licensee registration is enabled" + "additionalProperties": false, + "description": "The query parameters" }, - "transactionFeeConfiguration": { + "sorting": { + "required": [ + "key" + ], "type": "object", "properties": { - "licenseeCharges": { - "required": [ - "active", - "chargeAmount", - "chargeType" - ], - "type": "object", - "properties": { - "chargeType": { - "type": "string", - "description": "The type of transaction fee charge", - "enum": [ - "FLAT_FEE_PER_PRIVILEGE" - ] - }, - "active": { - "type": "boolean", - "description": "Whether the compact is charging licensees transaction fees" - }, - "chargeAmount": { - "minimum": 0, - "type": "number", - "description": "The amount to charge per privilege purchased" - } - }, - "additionalProperties": false + "key": { + "type": "string", + "description": "The key to sort results by", + "enum": [ + "dateOfUpdate", + "familyName" + ] + }, + "direction": { + "type": "string", + "description": "Direction to sort results by", + "enum": [ + "ascending", + "descending" + ] } }, - "additionalProperties": false - }, - "compactOperationsTeamEmails": { - "maxItems": 10, - "minItems": 1, - "uniqueItems": true, - "type": "array", - "description": "List of email addresses for operations team notifications", - "items": { - "type": "string", - "format": "email" - } - } - }, - "additionalProperties": false - }, - "SandboLiceno4zXe3APQ07E": { - "required": [ - "enabled" - ], - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Whether the feature flag is enabled" - } - } - }, - "SandboLicenwjDvDRZGRaqT": { - "required": [ - "effectiveLiftDate" - ], - "type": "object", - "properties": { - "effectiveLiftDate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "description": "The effective date when the encumbrance will be lifted", - "format": "date" + "description": "How to sort results" } }, "additionalProperties": false }, - "SandboLicenfM0YyXB9i4El": { + "SandboLicenHX8YNrOpPZbZ": { "type": "array", "items": { "required": [ @@ -3620,21 +3525,149 @@ } } }, - "SandboLicenUTb3zNObrjW5": { + "SandboLicengeanQqZ1NjKt": { "required": [ - "deactivationNote" + "message" ], "type": "object", "properties": { - "deactivationNote": { - "maxLength": 256, + "message": { "type": "string", - "description": "Note describing why the privilege is being deactivated" + "description": "A message about the request" + } + } + }, + "SandboLicenTi9BrhAERjPp": { + "required": [ + "jurisdictionAdverseActionsNotificationEmails", + "jurisdictionOperationsTeamEmails", + "jurisdictionSummaryReportNotificationEmails", + "jurisprudenceRequirements", + "licenseeRegistrationEnabled", + "privilegeFees" + ], + "type": "object", + "properties": { + "privilegeFees": { + "type": "array", + "description": "The fees for the privileges by license type", + "items": { + "required": [ + "amount", + "licenseTypeAbbreviation" + ], + "type": "object", + "properties": { + "amount": { + "minimum": 0, + "type": "number" + }, + "militaryRate": { + "description": "Optional military rate for the privilege fee.", + "oneOf": [ + { + "minimum": 0, + "type": "number" + }, + null + ] + }, + "licenseTypeAbbreviation": { + "type": "string", + "enum": [ + "aud", + "slp", + "ot", + "ota", + "lpc" + ] + } + }, + "additionalProperties": false + } + }, + "jurisdictionAdverseActionsNotificationEmails": { + "maxItems": 10, + "minItems": 1, + "uniqueItems": true, + "type": "array", + "description": "List of email addresses for adverse actions notifications", + "items": { + "type": "string", + "format": "email" + } + }, + "jurisdictionOperationsTeamEmails": { + "maxItems": 10, + "minItems": 1, + "uniqueItems": true, + "type": "array", + "description": "List of email addresses for operations team notifications", + "items": { + "type": "string", + "format": "email" + } + }, + "jurisprudenceRequirements": { + "required": [ + "required" + ], + "type": "object", + "properties": { + "linkToDocumentation": { + "description": "Optional link to jurisprudence documentation", + "oneOf": [ + { + "type": "string" + }, + null + ] + }, + "required": { + "type": "boolean", + "description": "Whether jurisprudence requirements exist" + } + }, + "additionalProperties": false + }, + "licenseeRegistrationEnabled": { + "type": "boolean", + "description": "Denotes whether licensee registration is enabled" + }, + "jurisdictionSummaryReportNotificationEmails": { + "maxItems": 10, + "minItems": 1, + "uniqueItems": true, + "type": "array", + "description": "List of email addresses for summary report notifications", + "items": { + "type": "string", + "format": "email" + } + } + }, + "additionalProperties": false + }, + "SandboLicen3zolh21hPpCk": { + "required": [ + "effectiveLiftDate" + ], + "type": "object", + "properties": { + "effectiveLiftDate": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "description": "The effective date when the encumbrance will be lifted", + "format": "date" } }, "additionalProperties": false }, - "SandboLicenjksXkBC6cpMT": { + "SandboLicenBxXRobaYJSR9": { + "required": [ + "pagination", + "providers" + ], "type": "object", "properties": { "pagination": { @@ -3657,255 +3690,6 @@ } } }, - "users": { - "type": "array", - "items": { - "required": [ - "attributes", - "permissions", - "status", - "userId" - ], - "type": "object", - "properties": { - "permissions": { - "type": "object", - "additionalProperties": { - "type": "object", - "properties": { - "actions": { - "type": "object", - "properties": { - "readPrivate": { - "type": "boolean" - }, - "admin": { - "type": "boolean" - }, - "readSSN": { - "type": "boolean" - } - } - }, - "jurisdictions": { - "type": "object", - "additionalProperties": { - "type": "object", - "properties": { - "actions": { - "type": "object", - "properties": { - "readPrivate": { - "type": "boolean" - }, - "admin": { - "type": "boolean" - }, - "write": { - "type": "boolean" - }, - "readSSN": { - "type": "boolean" - } - }, - "additionalProperties": false - } - } - } - } - }, - "additionalProperties": false - } - }, - "attributes": { - "required": [ - "email", - "familyName", - "givenName" - ], - "type": "object", - "properties": { - "givenName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "familyName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "email": { - "maxLength": 100, - "minLength": 5, - "type": "string" - } - }, - "additionalProperties": false - }, - "userId": { - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - } - }, - "additionalProperties": false - } - } - }, - "additionalProperties": false - }, - "SandboLicenuLEzkOrUfJeb": { - "required": [ - "attributes", - "permissions", - "status", - "userId" - ], - "type": "object", - "properties": { - "permissions": { - "type": "object", - "additionalProperties": { - "type": "object", - "properties": { - "actions": { - "type": "object", - "properties": { - "readPrivate": { - "type": "boolean" - }, - "admin": { - "type": "boolean" - }, - "readSSN": { - "type": "boolean" - } - } - }, - "jurisdictions": { - "type": "object", - "additionalProperties": { - "type": "object", - "properties": { - "actions": { - "type": "object", - "properties": { - "readPrivate": { - "type": "boolean" - }, - "admin": { - "type": "boolean" - }, - "write": { - "type": "boolean" - }, - "readSSN": { - "type": "boolean" - } - }, - "additionalProperties": false - } - } - } - } - }, - "additionalProperties": false - } - }, - "attributes": { - "required": [ - "email", - "familyName", - "givenName" - ], - "type": "object", - "properties": { - "givenName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "familyName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "email": { - "maxLength": 100, - "minLength": 5, - "type": "string" - } - }, - "additionalProperties": false - }, - "userId": { - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - } - }, - "additionalProperties": false - }, - "SandboLicenOJHHzc75m2Dh": { - "required": [ - "affiliationType", - "fileNames" - ], - "type": "object", - "properties": { - "affiliationType": { - "type": "string", - "description": "The type of military affiliation", - "enum": [ - "militaryMember", - "militaryMemberSpouse" - ] - }, - "fileNames": { - "type": "array", - "description": "List of military affiliation file names", - "items": { - "maxLength": 150, - "type": "string", - "description": "The name of the file being uploaded" - } - } - }, - "additionalProperties": false - }, - "SandboLicen14sjN2dXfQbr": { - "required": [ - "query" - ], - "type": "object", - "properties": { - "pagination": { - "type": "object", - "properties": { - "lastKey": { - "maxLength": 1024, - "minLength": 1, - "type": "string" - }, - "pageSize": { - "maximum": 100, - "minimum": 5, - "type": "integer" - } - }, - "additionalProperties": false - }, "query": { "type": "object", "properties": { @@ -3976,16 +3760,14 @@ "givenName": { "maxLength": 100, "type": "string", - "description": "Filter for providers with a given name (familyName is required if givenName is provided)" + "description": "Filter for providers with a given name" }, "familyName": { "maxLength": 100, "type": "string", "description": "Filter for providers with a family name" } - }, - "additionalProperties": false, - "description": "The query parameters" + } }, "sorting": { "required": [ @@ -4011,257 +3793,18 @@ } }, "description": "How to sort results" - } - }, - "additionalProperties": false - }, - "SandboLicen34TBFk9LdAJJ": { - "required": [ - "verificationCode" - ], - "type": "object", - "properties": { - "verificationCode": { - "pattern": "^[0-9]{4}$", - "type": "string", - "description": "4-digit verification code" - } - }, - "additionalProperties": false - }, - "SandboLicenrZuAY9tZ4fCA": { - "required": [ - "transactionId" - ], - "type": "object", - "properties": { - "message": { - "type": "string", - "description": "A message about the transaction" - }, - "transactionId": { - "type": "string", - "description": "The transaction id for the purchase" - } - } - }, - "SandboLicenFBKwuejTjcRR": { - "type": "object", - "properties": { - "dateCreated": { - "type": "string", - "format": "date-time" - }, - "attestationId": { - "type": "string" - }, - "compact": { - "type": "string", - "enum": [ - "aslp", - "octp", - "coun" - ] }, - "text": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "attestation" - ] - }, - "locale": { - "type": "string" - }, - "version": { - "type": "string" - }, - "required": { - "type": "boolean" - } - } - }, - "SandboLicenT1eMkx5ptE5k": { - "required": [ - "message" - ], - "type": "object", - "properties": { - "message": { - "type": "string", - "description": "A message about the request" - } - } - }, - "SandboLicen2TYOnVghC533": { - "required": [ - "compact", - "dob", - "familyName", - "givenName", - "jurisdiction", - "licenseType", - "partialSocial", - "password", - "recaptchaToken", - "username" - ], - "type": "object", - "properties": { - "licenseType": { - "type": "string", - "description": "Type of license", - "enum": [ - "audiologist", - "speech-language pathologist", - "occupational therapist", - "occupational therapy assistant", - "licensed professional counselor" - ] - }, - "password": { - "maxLength": 256, - "minLength": 12, - "type": "string", - "description": "Provider's current password" - }, - "compact": { - "type": "string", - "description": "Compact abbreviation", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "dob": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "description": "Date of birth in YYYY-MM-DD format", - "format": "date" - }, - "jurisdiction": { - "type": "string", - "description": "Two-letter jurisdiction code", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "givenName": { - "maxLength": 200, - "minLength": 1, - "type": "string", - "description": "Provider's given name" - }, - "familyName": { - "maxLength": 200, - "minLength": 1, - "type": "string", - "description": "Provider's family name" - }, - "recaptchaToken": { - "minLength": 1, - "type": "string", - "description": "ReCAPTCHA token for verification" - }, - "partialSocial": { - "pattern": "^[0-9]{4}$", - "type": "string", - "description": "Last 4 digits of SSN" - }, - "username": { + "providers": { "maxLength": 100, - "minLength": 5, - "type": "string", - "description": "Provider's email address (username)", - "format": "email" - } - }, - "additionalProperties": false - }, - "SandboLicenkPqluVn6dJHe": { - "required": [ - "compact", - "dateOfUpdate", - "familyName", - "givenName", - "licenseJurisdiction", - "privilegeJurisdictions", - "providerId", - "type" - ], - "type": "object", - "properties": { - "privileges": { "type": "array", "items": { "required": [ - "administratorSetStatus", "compact", - "dateOfExpiration", - "dateOfIssuance", - "dateOfRenewal", - "dateOfUpdate", - "jurisdiction", + "familyName", + "givenName", "licenseJurisdiction", - "licenseType", - "privilegeId", + "privilegeJurisdictions", "providerId", - "status", "type" ], "type": "object", @@ -4332,1174 +3875,201 @@ "coun" ] }, - "jurisdiction": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] + "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" }, - "history": { - "type": "array", - "items": { - "required": [ - "compact", - "dateOfUpdate", - "jurisdiction", - "licenseType", - "previous", - "providerId", - "type", - "updateType", - "updatedValues" - ], - "type": "object", - "properties": { - "licenseType": { - "type": "string", - "enum": [ - "audiologist", - "speech-language pathologist", - "occupational therapist", - "occupational therapy assistant", - "licensed professional counselor" - ] - }, - "compact": { - "type": "string", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "previous": { - "required": [ - "administratorSetStatus", - "dateOfExpiration", - "dateOfIssuance", - "dateOfRenewal", - "dateOfUpdate", - "licenseJurisdiction", - "privilegeId" - ], - "type": "object", - "properties": { - "administratorSetStatus": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - }, - "dateOfExpiration": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "licenseJurisdiction": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "privilegeId": { - "type": "string" - }, - "dateOfRenewal": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "dateOfIssuance": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "dateOfUpdate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - } - } - }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, - "jurisdiction": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "updatedValues": { - "type": "object", - "properties": { - "administratorSetStatus": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - }, - "dateOfExpiration": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "licenseJurisdiction": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "privilegeId": { - "type": "string" - }, - "dateOfRenewal": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "dateOfIssuance": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "dateOfUpdate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - } - } - }, - "type": { - "type": "string", - "enum": [ - "privilegeUpdate" - ] - }, - "dateOfUpdate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "updateType": { - "type": "string", - "enum": [ - "deactivation", - "expiration", - "issuance", - "other", - "renewal", - "encumbrance", - "homeJurisdictionChange", - "registration", - "lifting_encumbrance", - "licenseDeactivation", - "emailChange" - ] - } - } - } - }, - "type": { - "type": "string", - "enum": [ - "privilege" - ] - }, - "dateOfIssuance": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "licenseType": { - "type": "string", - "enum": [ - "audiologist", - "speech-language pathologist", - "occupational therapist", - "occupational therapy assistant", - "licensed professional counselor" - ] - }, - "administratorSetStatus": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - }, - "dateOfExpiration": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "privilegeId": { - "type": "string" - }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, - "dateOfRenewal": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "adverseActions": { - "type": "array", - "items": { - "required": [ - "actionAgainst", - "adverseActionId", - "compact", - "creationDate", - "dateOfUpdate", - "effectiveStartDate", - "jurisdiction", - "licenseType", - "licenseTypeAbbreviation", - "providerId", - "type" - ], - "type": "object", - "properties": { - "licenseType": { - "type": "string" - }, - "compact": { - "type": "string", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, - "jurisdiction": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "effectiveStartDate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "licenseTypeAbbreviation": { - "type": "string" - }, - "adverseActionId": { - "type": "string" - }, - "effectiveLiftDate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "type": { - "type": "string", - "enum": [ - "adverseAction" - ] - }, - "creationDate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "actionAgainst": { - "type": "string" - }, - "dateOfUpdate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - } - } - } - }, - "dateOfUpdate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "status": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - } - } - } - }, - "licenseJurisdiction": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "compact": { - "type": "string", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, - "givenName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "privilegeJurisdictions": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - } - }, - "type": { - "type": "string", - "enum": [ - "provider" - ] - }, - "suffix": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "currentHomeJurisdiction": { - "type": "string", - "description": "The current jurisdiction postal abbreviation if known.", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy", - "other", - "unknown" - ] - }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, - "familyName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "middleName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "dateOfUpdate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - } - } - }, - "SandboLicenaLbwv28ZqyH6": { - "required": [ - "message" - ], - "type": "object", - "properties": { - "message": { - "type": "string", - "description": "A message about the request" - } - } - }, - "SandboLicen74bwm5yy6E5W": { - "required": [ - "apiLoginId", - "processor", - "transactionKey" - ], - "type": "object", - "properties": { - "apiLoginId": { - "maxLength": 100, - "minLength": 1, - "type": "string", - "description": "The api login id for the payment processor" - }, - "transactionKey": { - "maxLength": 100, - "minLength": 1, - "type": "string", - "description": "The transaction key for the payment processor" - }, - "processor": { - "type": "string", - "description": "The type of payment processor", - "enum": [ - "authorize.net" - ] - } - }, - "additionalProperties": false - }, - "SandboLicenZVm9nad5vQW4": { - "required": [ - "compact", - "events", - "jurisdiction", - "licenseType", - "privilegeId", - "providerId" - ], - "type": "object", - "properties": { - "licenseType": { - "type": "string", - "enum": [ - "audiologist", - "speech-language pathologist", - "occupational therapist", - "occupational therapy assistant", - "licensed professional counselor" - ] - }, - "compact": { - "type": "string", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "privilegeId": { - "type": "string" - }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, - "jurisdiction": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "events": { - "type": "array", - "items": { - "required": [ - "createDate", - "dateOfUpdate", - "effectiveDate", - "type", - "updateType" - ], - "type": "object", - "properties": { - "note": { + "npi": { + "pattern": "^[0-9]{10}$", + "type": "string" + }, + "givenName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "familyName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "middleName": { + "maxLength": 100, + "minLength": 1, "type": "string" }, + "privilegeJurisdictions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" + ] + } + }, "type": { "type": "string", "enum": [ - "privilegeUpdate" - ] - }, - "dateOfUpdate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "effectiveDate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "updateType": { - "type": "string", - "enum": [ - "deactivation", - "expiration", - "issuance", - "other", - "renewal", - "encumbrance", - "homeJurisdictionChange", - "registration", - "lifting_encumbrance", - "licenseDeactivation", - "emailChange" + "provider" ] }, - "createDate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - } - } - } - } - } - }, - "SandboLicen3QxrBb8JFDnT": { - "required": [ - "encumbranceEffectiveDate", - "encumbranceType" - ], - "type": "object", - "properties": { - "encumbranceEffectiveDate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "description": "The effective date of the encumbrance", - "format": "date" - }, - "clinicalPrivilegeActionCategories": { - "type": "array", - "description": "The categories of clinical privilege action", - "items": { - "type": "string" - } - }, - "encumbranceType": { - "type": "string", - "description": "The type of encumbrance", - "enum": [ - "fine", - "reprimand", - "required supervision", - "completion of continuing education", - "public reprimand", - "probation", - "injunctive action", - "suspension", - "revocation", - "denial", - "surrender of license", - "modification of previous action-extension", - "modification of previous action-reduction", - "other monitoring", - "other adjudicated action not listed" - ] - }, - "clinicalPrivilegeActionCategory": { - "type": "string", - "description": "(Deprecated) The category of clinical privilege action. Use clinicalPrivilegeActionCategories instead." - } - }, - "additionalProperties": false - }, - "SandboLicene2a50HkoiVOX": { - "required": [ - "compact", - "jurisdictionAdverseActionsNotificationEmails", - "jurisdictionName", - "jurisdictionOperationsTeamEmails", - "jurisdictionSummaryReportNotificationEmails", - "jurisprudenceRequirements", - "licenseeRegistrationEnabled", - "postalAbbreviation", - "privilegeFees" - ], - "type": "object", - "properties": { - "privilegeFees": { - "type": "array", - "description": "The fees for the privileges by license type", - "items": { - "required": [ - "amount", - "licenseTypeAbbreviation" - ], - "type": "object", - "properties": { - "amount": { - "type": "number" - }, - "militaryRate": { - "description": "Optional military rate for the privilege fee.", - "oneOf": [ - { - "minimum": 0, - "type": "number" - }, - null - ] + "suffix": { + "maxLength": 100, + "minLength": 1, + "type": "string" }, - "licenseTypeAbbreviation": { + "currentHomeJurisdiction": { "type": "string", + "description": "The current jurisdiction postal abbreviation if known.", "enum": [ - "aud", - "slp", - "ot", - "ota", - "lpc" - ] - } - } - } - }, - "postalAbbreviation": { - "type": "string", - "description": "The postal abbreviation of the jurisdiction" - }, - "jurisdictionAdverseActionsNotificationEmails": { - "type": "array", - "description": "List of email addresses for adverse actions notifications", - "items": { - "type": "string", - "format": "email" - } - }, - "jurisdictionOperationsTeamEmails": { - "type": "array", - "description": "List of email addresses for operations team notifications", - "items": { - "type": "string", - "format": "email" - } - }, - "compact": { - "type": "string", - "description": "The compact this jurisdiction configuration belongs to", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "jurisprudenceRequirements": { - "required": [ - "required" - ], - "type": "object", - "properties": { - "linkToDocumentation": { - "description": "Optional link to jurisprudence documentation", - "oneOf": [ - { - "type": "string" - }, - null - ] - }, - "required": { - "type": "boolean", - "description": "Whether jurisprudence requirements exist" + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy", + "other", + "unknown" + ] + }, + "dateOfUpdate": { + "type": "string", + "format": "date-time" + } } } - }, - "licenseeRegistrationEnabled": { - "type": "boolean", - "description": "Denotes whether licensee registration is enabled" - }, - "jurisdictionName": { + } + } + }, + "SandboLicenou5OKotlJ6Ez": { + "required": [ + "message" + ], + "type": "object", + "properties": { + "message": { "type": "string", - "description": "The name of the jurisdiction" - }, - "jurisdictionSummaryReportNotificationEmails": { - "type": "array", - "description": "List of email addresses for summary report notifications", - "items": { - "type": "string", - "format": "email" - } + "description": "A message about the request" } } }, - "SandboLicenLhkhDxX4fiIg": { + "SandboLicenpPZOnBEPSaJL": { "required": [ - "attestations", + "compact", + "dob", + "familyName", + "givenName", + "jurisdiction", "licenseType", - "orderInformation", - "selectedJurisdictions" + "partialSocial", + "password", + "recaptchaToken", + "username" ], "type": "object", "properties": { "licenseType": { "type": "string", - "description": "The type of license the provider is purchasing a privilege for.", + "description": "Type of license", "enum": [ "audiologist", "speech-language pathologist", @@ -5508,151 +4078,201 @@ "licensed professional counselor" ] }, - "attestations": { - "type": "array", - "description": "List of attestations that the user has agreed to", - "items": { - "required": [ - "attestationId", - "version" - ], - "type": "object", - "properties": { - "attestationId": { - "maxLength": 100, - "type": "string", - "description": "The ID of the attestation" - }, - "version": { - "maxLength": 10, - "pattern": "^\\d+$", - "type": "string", - "description": "The version of the attestation" - } - } - } + "password": { + "maxLength": 256, + "minLength": 12, + "type": "string", + "description": "Provider's current password" }, - "orderInformation": { - "required": [ - "opaqueData" - ], - "type": "object", - "properties": { - "opaqueData": { - "required": [ - "dataDescriptor", - "dataValue" - ], - "type": "object", - "properties": { - "dataValue": { - "maxLength": 1000, - "type": "string", - "description": "The opaque data value token returned by Authorize.Net Accept UI" - }, - "dataDescriptor": { - "maxLength": 100, - "type": "string", - "description": "The opaque data descriptor returned by Authorize.Net Accept UI" - } - } - } - } + "compact": { + "type": "string", + "description": "Compact abbreviation", + "enum": [ + "aslp", + "octp", + "coun" + ] }, - "selectedJurisdictions": { - "maxLength": 20, - "type": "array", - "items": { - "type": "string", - "description": "Jurisdictions a provider has selected to purchase privileges in.", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - } + "dob": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "description": "Date of birth in YYYY-MM-DD format", + "format": "date" + }, + "jurisdiction": { + "type": "string", + "description": "Two-letter jurisdiction code", + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" + ] + }, + "givenName": { + "maxLength": 200, + "minLength": 1, + "type": "string", + "description": "Provider's given name" + }, + "familyName": { + "maxLength": 200, + "minLength": 1, + "type": "string", + "description": "Provider's family name" + }, + "recaptchaToken": { + "minLength": 1, + "type": "string", + "description": "ReCAPTCHA token for verification" + }, + "partialSocial": { + "pattern": "^[0-9]{4}$", + "type": "string", + "description": "Last 4 digits of SSN" + }, + "username": { + "maxLength": 100, + "minLength": 5, + "type": "string", + "description": "Provider's email address (username)", + "format": "email" } - } + }, + "additionalProperties": false + }, + "SandboLicenHVkU4oNl0nsF": { + "type": "object", + "properties": { + "context": { + "type": "object", + "properties": { + "userId": { + "maxLength": 100, + "minLength": 1, + "type": "string", + "description": "Optional user ID for feature flag evaluation" + }, + "customAttributes": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Optional custom attributes for feature flag evaluation" + } + }, + "additionalProperties": false, + "description": "Optional context for feature flag evaluation" + } + }, + "additionalProperties": false }, - "SandboLicenDDeRKnGCOQFv": { + "SandboLicenEAecRKtirVZk": { "required": [ - "status" + "encumbranceEffectiveDate", + "encumbranceType" ], "type": "object", "properties": { - "status": { + "encumbranceEffectiveDate": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", - "description": "The status to set the military affiliation to.", + "description": "The effective date of the encumbrance", + "format": "date" + }, + "clinicalPrivilegeActionCategories": { + "type": "array", + "description": "The categories of clinical privilege action", + "items": { + "type": "string" + } + }, + "encumbranceType": { + "type": "string", + "description": "The type of encumbrance", "enum": [ - "inactive" + "fine", + "reprimand", + "required supervision", + "completion of continuing education", + "public reprimand", + "probation", + "injunctive action", + "suspension", + "revocation", + "denial", + "surrender of license", + "modification of previous action-extension", + "modification of previous action-reduction", + "other monitoring", + "other adjudicated action not listed" ] + }, + "clinicalPrivilegeActionCategory": { + "type": "string", + "description": "(Deprecated) The category of clinical privilege action. Use clinicalPrivilegeActionCategories instead." } }, - "additionalProperties": false + "additionalProperties": false, + "description": "Encumbrance data to create" }, - "SandboLicen6yHAPPYOdJ3t": { + "SandboLicendF8CsBCXAU1x": { "required": [ - "birthMonthDay", "compact", - "dateOfExpiration", "dateOfUpdate", "familyName", "givenName", "licenseJurisdiction", - "licenses", - "militaryAffiliations", "privilegeJurisdictions", - "privileges", "providerId", "type" ], @@ -5663,14 +4283,11 @@ "items": { "required": [ "administratorSetStatus", - "attestations", "compact", - "compactTransactionId", "dateOfExpiration", "dateOfIssuance", "dateOfRenewal", "dateOfUpdate", - "history", "jurisdiction", "licenseJurisdiction", "licenseType", @@ -5805,26 +4422,6 @@ "wy" ] }, - "attestations": { - "type": "array", - "items": { - "required": [ - "attestationId", - "version" - ], - "type": "object", - "properties": { - "attestationId": { - "maxLength": 100, - "type": "string" - }, - "version": { - "maxLength": 100, - "type": "string" - } - } - } - }, "history": { "type": "array", "items": { @@ -5832,19 +4429,15 @@ "compact", "dateOfUpdate", "jurisdiction", + "licenseType", "previous", + "providerId", "type", - "updateType" + "updateType", + "updatedValues" ], "type": "object", "properties": { - "removedValues": { - "type": "array", - "description": "List of field names that were present in the previous record but removed in the update", - "items": { - "type": "string" - } - }, "licenseType": { "type": "string", "enum": [ @@ -5866,8 +4459,6 @@ "previous": { "required": [ "administratorSetStatus", - "attestations", - "compactTransactionId", "dateOfExpiration", "dateOfIssuance", "dateOfRenewal", @@ -5877,73 +4468,19 @@ ], "type": "object", "properties": { - "licenseJurisdiction": { + "administratorSetStatus": { "type": "string", "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" + "active", + "inactive" ] }, - "compact": { + "dateOfExpiration": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", - "enum": [ - "aslp", - "octp", - "coun" - ] + "format": "date" }, - "jurisdiction": { + "licenseJurisdiction": { "type": "string", "enum": [ "al", @@ -5989,90 +4526,41 @@ "ri", "sc", "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "attestations": { - "type": "array", - "items": { - "required": [ - "attestationId", - "version" - ], - "type": "object", - "properties": { - "attestationId": { - "maxLength": 100, - "type": "string" - }, - "version": { - "maxLength": 100, - "type": "string" - } - } - } - }, - "type": { - "type": "string", - "enum": [ - "privilege" - ] - }, - "compactTransactionId": { - "type": "string" - }, - "dateOfIssuance": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "administratorSetStatus": { - "type": "string", - "enum": [ - "active", - "inactive" + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" ] }, - "dateOfExpiration": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, "privilegeId": { "type": "string" }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, "dateOfRenewal": { "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", "format": "date" }, - "dateOfUpdate": { + "dateOfIssuance": { "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", "format": "date" }, - "status": { + "dateOfUpdate": { "type": "string", - "enum": [ - "active", - "inactive" - ] + "format": "date-time" } } }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" + }, "jurisdiction": { "type": "string", "enum": [ @@ -6134,73 +4622,19 @@ "updatedValues": { "type": "object", "properties": { - "licenseJurisdiction": { + "administratorSetStatus": { "type": "string", "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" + "active", + "inactive" ] }, - "compact": { + "dateOfExpiration": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", - "enum": [ - "aslp", - "octp", - "coun" - ] + "format": "date" }, - "jurisdiction": { + "licenseJurisdiction": { "type": "string", "enum": [ "al", @@ -6258,75 +4692,22 @@ "wy" ] }, - "attestations": { - "type": "array", - "items": { - "required": [ - "attestationId", - "version" - ], - "type": "object", - "properties": { - "attestationId": { - "maxLength": 100, - "type": "string" - }, - "version": { - "maxLength": 100, - "type": "string" - } - } - } - }, - "type": { - "type": "string", - "enum": [ - "privilege" - ] - }, - "compactTransactionId": { - "type": "string" - }, - "dateOfIssuance": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "administratorSetStatus": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - }, - "dateOfExpiration": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, "privilegeId": { "type": "string" }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, "dateOfRenewal": { "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", "format": "date" }, - "dateOfUpdate": { + "dateOfIssuance": { "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", "format": "date" }, - "status": { + "dateOfUpdate": { "type": "string", - "enum": [ - "active", - "inactive" - ] + "format": "date-time" } } }, @@ -6337,9 +4718,8 @@ ] }, "dateOfUpdate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", - "format": "date" + "format": "date-time" }, "updateType": { "type": "string", @@ -6366,9 +4746,6 @@ "privilege" ] }, - "compactTransactionId": { - "type": "string" - }, "dateOfIssuance": { "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", @@ -6418,7 +4795,6 @@ "creationDate", "dateOfUpdate", "effectiveStartDate", - "encumbranceType", "jurisdiction", "licenseType", "licenseTypeAbbreviation", @@ -6427,12 +4803,8 @@ ], "type": "object", "properties": { - "clinicalPrivilegeActionCategories": { - "type": "array", - "description": "The categories of clinical privilege action", - "items": { - "type": "string" - } + "licenseType": { + "type": "string" }, "compact": { "type": "string", @@ -6442,6 +4814,10 @@ "coun" ] }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" + }, "jurisdiction": { "type": "string", "enum": [ @@ -6500,64 +4876,46 @@ "wy" ] }, - "licenseTypeAbbreviation": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "adverseAction" - ] - }, - "creationDate": { + "effectiveStartDate": { "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", "format": "date" }, - "actionAgainst": { - "type": "string" - }, - "licenseType": { + "licenseTypeAbbreviation": { "type": "string" }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "adverseActionId": { "type": "string" }, - "effectiveStartDate": { + "effectiveLiftDate": { "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", "format": "date" }, - "adverseActionId": { - "type": "string" + "type": { + "type": "string", + "enum": [ + "adverseAction" + ] }, - "effectiveLiftDate": { + "creationDate": { "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", "format": "date" }, - "encumbranceType": { - "type": "string" - }, - "clinicalPrivilegeActionCategory": { - "type": "string" - }, - "liftingUser": { + "actionAgainst": { "type": "string" }, "dateOfUpdate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", - "format": "date" + "format": "date-time" } } } }, "dateOfUpdate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", - "format": "date" + "format": "date-time" }, "status": { "type": "string", @@ -6629,47 +4987,21 @@ }, "compact": { "type": "string", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, - "givenName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "compactEligibility": { - "type": "string", - "enum": [ - "eligible", - "ineligible" - ] - }, - "jurisdictionUploadedCompactEligibility": { - "type": "string", - "enum": [ - "eligible", - "ineligible" - ] - }, - "dateOfBirth": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "jurisdictionUploadedLicenseStatus": { - "type": "string", - "enum": [ - "active", - "inactive" + "enum": [ + "aslp", + "octp", + "coun" ] }, + "npi": { + "pattern": "^[0-9]{10}$", + "type": "string" + }, + "givenName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, "privilegeJurisdictions": { "type": "array", "items": { @@ -6788,1470 +5120,3200 @@ "pr", "ri", "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy", - "other", - "unknown" - ] - }, - "licenses": { - "type": "array", - "items": { - "required": [ - "compact", - "compactEligibility", - "dateOfExpiration", - "dateOfIssuance", - "dateOfRenewal", - "dateOfUpdate", - "familyName", - "givenName", - "history", - "homeAddressCity", - "homeAddressPostalCode", - "homeAddressState", - "homeAddressStreet1", - "jurisdiction", - "jurisdictionUploadedCompactEligibility", - "jurisdictionUploadedLicenseStatus", - "licenseStatus", - "licenseType", - "middleName", - "providerId", - "type" - ], - "type": "object", - "properties": { - "compact": { - "type": "string", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "homeAddressStreet2": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "jurisdiction": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "homeAddressStreet1": { - "maxLength": 100, - "minLength": 2, - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "license-home" - ] - }, - "suffix": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "dateOfIssuance": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "licenseType": { - "type": "string", - "enum": [ - "audiologist", - "speech-language pathologist", - "occupational therapist", - "occupational therapy assistant", - "licensed professional counselor" - ] - }, - "emailAddress": { - "maxLength": 100, - "minLength": 5, - "type": "string", - "format": "email" - }, - "dateOfExpiration": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "homeAddressState": { - "maxLength": 100, - "minLength": 2, - "type": "string" - }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, - "dateOfRenewal": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "familyName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "homeAddressCity": { - "maxLength": 100, - "minLength": 2, - "type": "string" - }, - "licenseNumber": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, - "homeAddressPostalCode": { - "maxLength": 7, - "minLength": 5, - "type": "string" - }, - "compactEligibility": { - "type": "string", - "enum": [ - "eligible", - "ineligible" - ] - }, - "givenName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "jurisdictionUploadedCompactEligibility": { - "type": "string", - "enum": [ - "eligible", - "ineligible" - ] - }, - "dateOfBirth": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "jurisdictionUploadedLicenseStatus": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - }, - "history": { - "type": "array", - "items": { - "required": [ - "compact", - "dateOfUpdate", - "jurisdiction", - "previous", - "type", - "updateType" - ], - "type": "object", - "properties": { - "removedValues": { - "type": "array", - "description": "List of field names that were present in the previous record but removed in the update", - "items": { - "type": "string" + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy", + "other", + "unknown" + ] + }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" + }, + "familyName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "middleName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "dateOfUpdate": { + "type": "string", + "format": "date-time" + } + } + }, + "SandboLicenI2vqm7fRdSBi": { + "required": [ + "items", + "pagination" + ], + "type": "object", + "properties": { + "pagination": { + "type": "object", + "properties": { + "prevLastKey": { + "maxLength": 1024, + "minLength": 1, + "type": "object" + }, + "lastKey": { + "maxLength": 1024, + "minLength": 1, + "type": "object" + }, + "pageSize": { + "maximum": 100, + "minimum": 5, + "type": "integer" + } + } + }, + "items": { + "maxLength": 100, + "type": "array", + "items": { + "type": "object", + "oneOf": [ + { + "required": [ + "compactAbbr", + "compactCommissionFee", + "compactName", + "isSandbox", + "paymentProcessorPublicFields", + "transactionFeeConfiguration", + "type" + ], + "type": "object", + "properties": { + "compactCommissionFee": { + "required": [ + "feeAmount", + "feeType" + ], + "type": "object", + "properties": { + "feeAmount": { + "type": "number" + }, + "feeType": { + "type": "string", + "enum": [ + "FLAT_RATE" + ] } - }, - "licenseType": { - "type": "string", - "enum": [ - "audiologist", - "speech-language pathologist", - "occupational therapist", - "occupational therapy assistant", - "licensed professional counselor" - ] - }, - "compact": { - "type": "string", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "previous": { - "required": [ - "dateOfExpiration", - "dateOfIssuance", - "dateOfRenewal", - "familyName", - "givenName", - "homeAddressCity", - "homeAddressPostalCode", - "homeAddressState", - "homeAddressStreet1", - "jurisdictionUploadedCompactEligibility", - "jurisdictionUploadedLicenseStatus", - "middleName" - ], - "type": "object", - "properties": { - "homeAddressStreet2": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, - "homeAddressPostalCode": { - "maxLength": 7, - "minLength": 5, - "type": "string" - }, - "givenName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "homeAddressStreet1": { - "maxLength": 100, - "minLength": 2, - "type": "string" - }, - "compactEligibility": { - "type": "string", - "enum": [ - "eligible", - "ineligible" - ] - }, - "jurisdictionUploadedCompactEligibility": { - "type": "string", - "enum": [ - "eligible", - "ineligible" - ] - }, - "dateOfBirth": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "jurisdictionUploadedLicenseStatus": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - }, - "suffix": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "dateOfIssuance": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "emailAddress": { - "maxLength": 100, - "minLength": 5, - "type": "string", - "format": "email" - }, - "dateOfExpiration": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "phoneNumber": { - "pattern": "^\\+[0-9]{8,15}$", - "type": "string" - }, - "homeAddressState": { - "maxLength": 100, - "minLength": 2, - "type": "string" - }, - "dateOfRenewal": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "licenseStatus": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - }, - "familyName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "homeAddressCity": { - "maxLength": 100, - "minLength": 2, - "type": "string" - }, - "licenseNumber": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "middleName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "licenseStatusName": { - "maxLength": 100, - "minLength": 1, - "type": "string" + } + }, + "compactAbbr": { + "type": "string", + "description": "The abbreviation of the compact" + }, + "paymentProcessorPublicFields": { + "required": [ + "apiLoginId", + "publicClientKey" + ], + "type": "object", + "properties": { + "publicClientKey": { + "type": "string", + "description": "The public client key for the payment processor" + }, + "apiLoginId": { + "type": "string", + "description": "The API login ID for the payment processor" + } + } + }, + "type": { + "type": "string", + "enum": [ + "compact" + ] + }, + "transactionFeeConfiguration": { + "required": [ + "licenseeCharges" + ], + "type": "object", + "properties": { + "licenseeCharges": { + "required": [ + "active", + "chargeAmount", + "chargeType" + ], + "type": "object", + "properties": { + "chargeType": { + "type": "string", + "description": "The type of transaction fee charge", + "enum": [ + "FLAT_FEE_PER_PRIVILEGE" + ] + }, + "active": { + "type": "boolean", + "description": "Whether the compact is charging licensees transaction fees" + }, + "chargeAmount": { + "type": "number", + "description": "The amount to charge per privilege purchased" + } } } - }, - "jurisdiction": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "updatedValues": { + } + }, + "isSandbox": { + "type": "boolean", + "description": "Whether the compact is in sandbox mode" + }, + "compactName": { + "type": "string", + "description": "The full name of the compact" + } + } + }, + { + "required": [ + "jurisdictionName", + "jurisprudenceRequirements", + "postalAbbreviation", + "privilegeFees", + "type" + ], + "type": "object", + "properties": { + "privilegeFees": { + "type": "array", + "description": "The fees for the privileges", + "items": { + "required": [ + "amount", + "licenseTypeAbbreviation" + ], "type": "object", "properties": { - "homeAddressStreet2": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, - "homeAddressPostalCode": { - "maxLength": 7, - "minLength": 5, - "type": "string" - }, - "givenName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "homeAddressStreet1": { - "maxLength": 100, - "minLength": 2, - "type": "string" - }, - "compactEligibility": { - "type": "string", - "enum": [ - "eligible", - "ineligible" - ] - }, - "jurisdictionUploadedCompactEligibility": { - "type": "string", - "enum": [ - "eligible", - "ineligible" - ] - }, - "dateOfBirth": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" + "amount": { + "type": "number" }, - "jurisdictionUploadedLicenseStatus": { - "type": "string", - "enum": [ - "active", - "inactive" + "militaryRate": { + "description": "Optional military rate for the privilege fee.", + "oneOf": [ + { + "minimum": 0, + "type": "number" + }, + null ] }, - "suffix": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "dateOfIssuance": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "emailAddress": { - "maxLength": 100, - "minLength": 5, - "type": "string", - "format": "email" - }, - "dateOfExpiration": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "phoneNumber": { - "pattern": "^\\+[0-9]{8,15}$", + "licenseTypeAbbreviation": { "type": "string" + } + } + } + }, + "postalAbbreviation": { + "type": "string", + "description": "The postal abbreviation of the jurisdiction" + }, + "jurisprudenceRequirements": { + "required": [ + "required" + ], + "type": "object", + "properties": { + "linkToDocumentation": { + "description": "Optional link to jurisprudence documentation", + "oneOf": [ + { + "type": "string" + }, + null + ] + }, + "required": { + "type": "boolean", + "description": "Whether jurisprudence requirements exist" + } + } + }, + "jurisdictionName": { + "type": "string", + "description": "The name of the jurisdiction" + }, + "type": { + "type": "string", + "enum": [ + "jurisdiction" + ] + } + } + } + ] + } + } + } + }, + "SandboLicenLPepHoMLi1aj": { + "required": [ + "apiLoginId", + "processor", + "transactionKey" + ], + "type": "object", + "properties": { + "apiLoginId": { + "maxLength": 100, + "minLength": 1, + "type": "string", + "description": "The api login id for the payment processor" + }, + "transactionKey": { + "maxLength": 100, + "minLength": 1, + "type": "string", + "description": "The transaction key for the payment processor" + }, + "processor": { + "type": "string", + "description": "The type of payment processor", + "enum": [ + "authorize.net" + ] + } + }, + "additionalProperties": false + }, + "SandboLicenmwNUQ4l5yt99": { + "required": [ + "action" + ], + "type": "object", + "properties": { + "action": { + "type": "string", + "enum": [ + "close" + ] + }, + "encumbrance": { + "required": [ + "encumbranceEffectiveDate", + "encumbranceType" + ], + "type": "object", + "properties": { + "encumbranceEffectiveDate": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "description": "The effective date of the encumbrance", + "format": "date" + }, + "clinicalPrivilegeActionCategories": { + "type": "array", + "description": "The categories of clinical privilege action", + "items": { + "type": "string" + } + }, + "encumbranceType": { + "type": "string", + "description": "The type of encumbrance", + "enum": [ + "fine", + "reprimand", + "required supervision", + "completion of continuing education", + "public reprimand", + "probation", + "injunctive action", + "suspension", + "revocation", + "denial", + "surrender of license", + "modification of previous action-extension", + "modification of previous action-reduction", + "other monitoring", + "other adjudicated action not listed" + ] + }, + "clinicalPrivilegeActionCategory": { + "type": "string", + "description": "(Deprecated) The category of clinical privilege action. Use clinicalPrivilegeActionCategories instead." + } + }, + "additionalProperties": false, + "description": "Encumbrance data to create" + } + } + }, + "SandboLicen0ONaFbyAwP9V": { + "required": [ + "enabled" + ], + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether the feature flag is enabled" + } + } + }, + "SandboLicenrzUhY3WW0Y7L": { + "required": [ + "compactAdverseActionsNotificationEmails", + "compactCommissionFee", + "compactOperationsTeamEmails", + "compactSummaryReportNotificationEmails", + "configuredStates", + "licenseeRegistrationEnabled" + ], + "type": "object", + "properties": { + "configuredStates": { + "type": "array", + "description": "List of states that have submitted configurations and their live status", + "items": { + "required": [ + "isLive", + "postalAbbreviation" + ], + "type": "object", + "properties": { + "postalAbbreviation": { + "type": "string", + "description": "The postal abbreviation of the jurisdiction", + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" + ] + }, + "isLive": { + "type": "boolean", + "description": "Whether the state is live and available for registrations." + } + }, + "additionalProperties": false + } + }, + "compactCommissionFee": { + "required": [ + "feeAmount", + "feeType" + ], + "type": "object", + "properties": { + "feeAmount": { + "minimum": 0, + "type": "number" + }, + "feeType": { + "type": "string", + "enum": [ + "FLAT_RATE" + ] + } + }, + "additionalProperties": false + }, + "compactSummaryReportNotificationEmails": { + "maxItems": 10, + "minItems": 1, + "uniqueItems": true, + "type": "array", + "description": "List of email addresses for summary report notifications", + "items": { + "type": "string", + "format": "email" + } + }, + "compactAdverseActionsNotificationEmails": { + "maxItems": 10, + "minItems": 1, + "uniqueItems": true, + "type": "array", + "description": "List of email addresses for adverse actions notifications", + "items": { + "type": "string", + "format": "email" + } + }, + "licenseeRegistrationEnabled": { + "type": "boolean", + "description": "Denotes whether licensee registration is enabled" + }, + "transactionFeeConfiguration": { + "type": "object", + "properties": { + "licenseeCharges": { + "required": [ + "active", + "chargeAmount", + "chargeType" + ], + "type": "object", + "properties": { + "chargeType": { + "type": "string", + "description": "The type of transaction fee charge", + "enum": [ + "FLAT_FEE_PER_PRIVILEGE" + ] + }, + "active": { + "type": "boolean", + "description": "Whether the compact is charging licensees transaction fees" + }, + "chargeAmount": { + "minimum": 0, + "type": "number", + "description": "The amount to charge per privilege purchased" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "compactOperationsTeamEmails": { + "maxItems": 10, + "minItems": 1, + "uniqueItems": true, + "type": "array", + "description": "List of email addresses for operations team notifications", + "items": { + "type": "string", + "format": "email" + } + } + }, + "additionalProperties": false + }, + "SandboLicenPAsEF1Ia4s7g": { + "required": [ + "deactivationNote" + ], + "type": "object", + "properties": { + "deactivationNote": { + "maxLength": 256, + "type": "string", + "description": "Note describing why the privilege is being deactivated" + } + }, + "additionalProperties": false + }, + "SandboLicenLLx7v2Sq2Nku": { + "type": "object", + "properties": { + "pagination": { + "type": "object", + "properties": { + "prevLastKey": { + "maxLength": 1024, + "minLength": 1, + "type": "object" + }, + "lastKey": { + "maxLength": 1024, + "minLength": 1, + "type": "object" + }, + "pageSize": { + "maximum": 100, + "minimum": 5, + "type": "integer" + } + } + }, + "users": { + "type": "array", + "items": { + "required": [ + "attributes", + "permissions", + "status", + "userId" + ], + "type": "object", + "properties": { + "permissions": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "actions": { + "type": "object", + "properties": { + "readPrivate": { + "type": "boolean" }, - "homeAddressState": { - "maxLength": 100, - "minLength": 2, - "type": "string" + "admin": { + "type": "boolean" }, - "dateOfRenewal": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" + "readSSN": { + "type": "boolean" + } + } + }, + "jurisdictions": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "actions": { + "type": "object", + "properties": { + "readPrivate": { + "type": "boolean" + }, + "admin": { + "type": "boolean" + }, + "write": { + "type": "boolean" + }, + "readSSN": { + "type": "boolean" + } + }, + "additionalProperties": false + } + } + } + } + }, + "additionalProperties": false + } + }, + "attributes": { + "required": [ + "email", + "familyName", + "givenName" + ], + "type": "object", + "properties": { + "givenName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "familyName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "email": { + "maxLength": 100, + "minLength": 5, + "type": "string" + } + }, + "additionalProperties": false + }, + "userId": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "active", + "inactive" + ] + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + }, + "SandboLicenobivPmYh5SvH": { + "required": [ + "compact", + "events", + "jurisdiction", + "licenseType", + "privilegeId", + "providerId" + ], + "type": "object", + "properties": { + "licenseType": { + "type": "string", + "enum": [ + "audiologist", + "speech-language pathologist", + "occupational therapist", + "occupational therapy assistant", + "licensed professional counselor" + ] + }, + "compact": { + "type": "string", + "enum": [ + "aslp", + "octp", + "coun" + ] + }, + "privilegeId": { + "type": "string" + }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" + }, + "jurisdiction": { + "type": "string", + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" + ] + }, + "events": { + "type": "array", + "items": { + "required": [ + "createDate", + "dateOfUpdate", + "effectiveDate", + "type", + "updateType" + ], + "type": "object", + "properties": { + "note": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "privilegeUpdate" + ] + }, + "dateOfUpdate": { + "type": "string", + "format": "date-time" + }, + "effectiveDate": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "updateType": { + "type": "string", + "enum": [ + "deactivation", + "expiration", + "issuance", + "other", + "renewal", + "encumbrance", + "homeJurisdictionChange", + "registration", + "lifting_encumbrance", + "licenseDeactivation", + "emailChange" + ] + }, + "createDate": { + "type": "string", + "format": "date-time" + } + } + } + } + } + }, + "SandboLicenI4CqYMff5SWl": { + "type": "object", + "properties": { + "attributes": { + "type": "object", + "properties": { + "givenName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "familyName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "SandboLicenZdwYMaXuEYs6": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" + ] + } + } + }, + "SandboLiceng72nqBB23bmL": { + "required": [ + "transactionId" + ], + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "A message about the transaction" + }, + "transactionId": { + "type": "string", + "description": "The transaction id for the purchase" + } + } + }, + "SandboLicenJZNKekBSUCl5": { + "required": [ + "affiliationType", + "fileNames" + ], + "type": "object", + "properties": { + "affiliationType": { + "type": "string", + "description": "The type of military affiliation", + "enum": [ + "militaryMember", + "militaryMemberSpouse" + ] + }, + "fileNames": { + "type": "array", + "description": "List of military affiliation file names", + "items": { + "maxLength": 150, + "type": "string", + "description": "The name of the file being uploaded" + } + } + }, + "additionalProperties": false + }, + "SandboLicenbGNGFVLD0EDE": { + "required": [ + "compact", + "jurisdictionAdverseActionsNotificationEmails", + "jurisdictionName", + "jurisdictionOperationsTeamEmails", + "jurisdictionSummaryReportNotificationEmails", + "jurisprudenceRequirements", + "licenseeRegistrationEnabled", + "postalAbbreviation", + "privilegeFees" + ], + "type": "object", + "properties": { + "privilegeFees": { + "type": "array", + "description": "The fees for the privileges by license type", + "items": { + "required": [ + "amount", + "licenseTypeAbbreviation" + ], + "type": "object", + "properties": { + "amount": { + "type": "number" + }, + "militaryRate": { + "description": "Optional military rate for the privilege fee.", + "oneOf": [ + { + "minimum": 0, + "type": "number" + }, + null + ] + }, + "licenseTypeAbbreviation": { + "type": "string", + "enum": [ + "aud", + "slp", + "ot", + "ota", + "lpc" + ] + } + } + } + }, + "postalAbbreviation": { + "type": "string", + "description": "The postal abbreviation of the jurisdiction" + }, + "jurisdictionAdverseActionsNotificationEmails": { + "type": "array", + "description": "List of email addresses for adverse actions notifications", + "items": { + "type": "string", + "format": "email" + } + }, + "jurisdictionOperationsTeamEmails": { + "type": "array", + "description": "List of email addresses for operations team notifications", + "items": { + "type": "string", + "format": "email" + } + }, + "compact": { + "type": "string", + "description": "The compact this jurisdiction configuration belongs to", + "enum": [ + "aslp", + "octp", + "coun" + ] + }, + "jurisprudenceRequirements": { + "required": [ + "required" + ], + "type": "object", + "properties": { + "linkToDocumentation": { + "description": "Optional link to jurisprudence documentation", + "oneOf": [ + { + "type": "string" + }, + null + ] + }, + "required": { + "type": "boolean", + "description": "Whether jurisprudence requirements exist" + } + } + }, + "licenseeRegistrationEnabled": { + "type": "boolean", + "description": "Denotes whether licensee registration is enabled" + }, + "jurisdictionName": { + "type": "string", + "description": "The name of the jurisdiction" + }, + "jurisdictionSummaryReportNotificationEmails": { + "type": "array", + "description": "List of email addresses for summary report notifications", + "items": { + "type": "string", + "format": "email" + } + } + } + }, + "SandboLicenC1PiOZAh5Usx": { + "type": "object", + "properties": { + "permissions": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "actions": { + "type": "object", + "properties": { + "readPrivate": { + "type": "boolean" + }, + "admin": { + "type": "boolean" + }, + "readSSN": { + "type": "boolean" + } + } + }, + "jurisdictions": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "actions": { + "type": "object", + "properties": { + "readPrivate": { + "type": "boolean" }, - "licenseStatus": { - "type": "string", - "enum": [ - "active", - "inactive" - ] + "admin": { + "type": "boolean" }, - "familyName": { - "maxLength": 100, - "minLength": 1, - "type": "string" + "write": { + "type": "boolean" }, - "homeAddressCity": { - "maxLength": 100, - "minLength": 2, - "type": "string" + "readSSN": { + "type": "boolean" + } + }, + "additionalProperties": false + } + } + } + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + }, + "SandboLicenHcuQxywUvhOh": { + "type": "object", + "properties": { + "dateCreated": { + "type": "string", + "format": "date-time" + }, + "attestationId": { + "type": "string" + }, + "compact": { + "type": "string", + "enum": [ + "aslp", + "octp", + "coun" + ] + }, + "text": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "attestation" + ] + }, + "locale": { + "type": "string" + }, + "version": { + "type": "string" + }, + "required": { + "type": "boolean" + } + } + }, + "SandboLicenDb8Dl04rAoPD": { + "required": [ + "attributes", + "permissions" + ], + "type": "object", + "properties": { + "permissions": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "actions": { + "type": "object", + "properties": { + "readPrivate": { + "type": "boolean" + }, + "admin": { + "type": "boolean" + }, + "readSSN": { + "type": "boolean" + } + } + }, + "jurisdictions": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "actions": { + "type": "object", + "properties": { + "readPrivate": { + "type": "boolean" }, - "licenseNumber": { - "maxLength": 100, - "minLength": 1, - "type": "string" + "admin": { + "type": "boolean" }, - "middleName": { - "maxLength": 100, - "minLength": 1, - "type": "string" + "write": { + "type": "boolean" }, - "licenseStatusName": { - "maxLength": 100, - "minLength": 1, - "type": "string" + "readSSN": { + "type": "boolean" } - } - }, - "type": { - "type": "string", - "enum": [ - "licenseUpdate" - ] - }, - "dateOfUpdate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "updateType": { - "type": "string", - "enum": [ - "deactivation", - "expiration", - "issuance", - "other", - "renewal", - "encumbrance", - "homeJurisdictionChange", - "registration", - "lifting_encumbrance", - "licenseDeactivation", - "emailChange" - ] + }, + "additionalProperties": false } } } + } + }, + "additionalProperties": false + } + }, + "attributes": { + "required": [ + "email", + "familyName", + "givenName" + ], + "type": "object", + "properties": { + "givenName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "familyName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "email": { + "maxLength": 100, + "minLength": 5, + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "SandboLicen8QcYRI7AzDyX": { + "required": [ + "jurisdiction" + ], + "type": "object", + "properties": { + "jurisdiction": { + "type": "string", + "description": "The jurisdiction postal abbreviation to set as home jurisdiction", + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy", + "other" + ] + } + }, + "additionalProperties": false + }, + "SandboLicenBWZgN83iyevY": { + "required": [ + "attestations", + "licenseType", + "orderInformation", + "selectedJurisdictions" + ], + "type": "object", + "properties": { + "licenseType": { + "type": "string", + "description": "The type of license the provider is purchasing a privilege for.", + "enum": [ + "audiologist", + "speech-language pathologist", + "occupational therapist", + "occupational therapy assistant", + "licensed professional counselor" + ] + }, + "attestations": { + "type": "array", + "description": "List of attestations that the user has agreed to", + "items": { + "required": [ + "attestationId", + "version" + ], + "type": "object", + "properties": { + "attestationId": { + "maxLength": 100, + "type": "string", + "description": "The ID of the attestation" + }, + "version": { + "maxLength": 10, + "pattern": "^\\d+$", + "type": "string", + "description": "The version of the attestation" + } + } + } + }, + "orderInformation": { + "required": [ + "opaqueData" + ], + "type": "object", + "properties": { + "opaqueData": { + "required": [ + "dataDescriptor", + "dataValue" + ], + "type": "object", + "properties": { + "dataValue": { + "maxLength": 1000, + "type": "string", + "description": "The opaque data value token returned by Authorize.Net Accept UI" + }, + "dataDescriptor": { + "maxLength": 100, + "type": "string", + "description": "The opaque data descriptor returned by Authorize.Net Accept UI" + } + } + } + } + }, + "selectedJurisdictions": { + "maxLength": 20, + "type": "array", + "items": { + "type": "string", + "description": "Jurisdictions a provider has selected to purchase privileges in.", + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" + ] + } + } + } + }, + "SandboLicenZ1SdRwpLvl46": { + "type": "object", + "properties": {} + }, + "SandboLicenBxfAXNgCl0f1": { + "required": [ + "compactAbbr", + "compactAdverseActionsNotificationEmails", + "compactCommissionFee", + "compactName", + "compactOperationsTeamEmails", + "compactSummaryReportNotificationEmails", + "configuredStates", + "licenseeRegistrationEnabled" + ], + "type": "object", + "properties": { + "configuredStates": { + "type": "array", + "description": "List of states that have submitted configurations and their live status", + "items": { + "required": [ + "isLive", + "postalAbbreviation" + ], + "type": "object", + "properties": { + "postalAbbreviation": { + "type": "string", + "description": "The postal abbreviation of the jurisdiction", + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" + ] + }, + "isLive": { + "type": "boolean", + "description": "Whether the state is live and available for registrations." + } + } + } + }, + "compactCommissionFee": { + "required": [ + "feeAmount", + "feeType" + ], + "type": "object", + "properties": { + "feeAmount": { + "type": "number" + }, + "feeType": { + "type": "string", + "enum": [ + "FLAT_RATE" + ] + } + } + }, + "compactSummaryReportNotificationEmails": { + "type": "array", + "description": "List of email addresses for summary report notifications", + "items": { + "type": "string", + "format": "email" + } + }, + "compactAdverseActionsNotificationEmails": { + "type": "array", + "description": "List of email addresses for adverse actions notifications", + "items": { + "type": "string", + "format": "email" + } + }, + "licenseeRegistrationEnabled": { + "type": "boolean", + "description": "Denotes whether licensee registration is enabled" + }, + "compactAbbr": { + "type": "string", + "description": "The abbreviation of the compact" + }, + "transactionFeeConfiguration": { + "type": "object", + "properties": { + "licenseeCharges": { + "required": [ + "active", + "chargeAmount", + "chargeType" + ], + "type": "object", + "properties": { + "chargeType": { + "type": "string", + "description": "The type of transaction fee charge", + "enum": [ + "FLAT_FEE_PER_PRIVILEGE" + ] + }, + "active": { + "type": "boolean", + "description": "Whether the compact is charging licensees transaction fees" + }, + "chargeAmount": { + "type": "number", + "description": "The amount to charge per privilege purchased" + } + } + } + } + }, + "compactName": { + "type": "string", + "description": "The full name of the compact" + }, + "compactOperationsTeamEmails": { + "type": "array", + "description": "List of email addresses for operations team notifications", + "items": { + "type": "string", + "format": "email" + } + } + } + }, + "SandboLiceno12FSfDWBzow": { + "required": [ + "pagination", + "providers" + ], + "type": "object", + "properties": { + "pagination": { + "type": "object", + "properties": { + "prevLastKey": { + "maxLength": 1024, + "minLength": 1, + "type": "object" + }, + "lastKey": { + "maxLength": 1024, + "minLength": 1, + "type": "object" + }, + "pageSize": { + "maximum": 100, + "minimum": 5, + "type": "integer" + } + } + }, + "sorting": { + "required": [ + "key" + ], + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "The key to sort results by", + "enum": [ + "dateOfUpdate", + "familyName" + ] + }, + "direction": { + "type": "string", + "description": "Direction to sort results by", + "enum": [ + "ascending", + "descending" + ] + } + }, + "description": "How to sort results" + }, + "providers": { + "maxLength": 100, + "type": "array", + "items": { + "required": [ + "birthMonthDay", + "compact", + "compactEligibility", + "dateOfExpiration", + "dateOfUpdate", + "familyName", + "givenName", + "jurisdictionUploadedCompactEligibility", + "jurisdictionUploadedLicenseStatus", + "licenseJurisdiction", + "licenseStatus", + "privilegeJurisdictions", + "providerId", + "type" + ], + "type": "object", + "properties": { + "licenseJurisdiction": { + "type": "string", + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" + ] + }, + "compact": { + "type": "string", + "enum": [ + "aslp", + "octp", + "coun" + ] + }, + "npi": { + "pattern": "^[0-9]{10}$", + "type": "string" + }, + "givenName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "compactEligibility": { + "type": "string", + "enum": [ + "eligible", + "ineligible" + ] }, - "ssnLastFour": { - "pattern": "^[0-9]{4}$", - "type": "string" + "jurisdictionUploadedCompactEligibility": { + "type": "string", + "enum": [ + "eligible", + "ineligible" + ] }, - "phoneNumber": { - "pattern": "^\\+[0-9]{8,15}$", - "type": "string" + "dateOfBirth": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" }, - "licenseStatus": { + "jurisdictionUploadedLicenseStatus": { "type": "string", "enum": [ "active", "inactive" ] }, - "middleName": { - "maxLength": 100, - "minLength": 1, - "type": "string" + "privilegeJurisdictions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" + ] + } }, - "licenseStatusName": { + "type": { + "type": "string", + "enum": [ + "provider" + ] + }, + "suffix": { "maxLength": 100, "minLength": 1, "type": "string" }, - "adverseActions": { - "type": "array", - "items": { - "required": [ - "actionAgainst", - "adverseActionId", - "compact", - "creationDate", - "dateOfUpdate", - "effectiveStartDate", - "encumbranceType", - "jurisdiction", - "licenseType", - "licenseTypeAbbreviation", - "providerId", - "type" - ], - "type": "object", - "properties": { - "clinicalPrivilegeActionCategories": { - "type": "array", - "description": "The categories of clinical privilege action", - "items": { - "type": "string" - } - }, - "compact": { - "type": "string", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "jurisdiction": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "licenseTypeAbbreviation": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "adverseAction" - ] - }, - "creationDate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "actionAgainst": { - "type": "string" - }, - "licenseType": { - "type": "string" - }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, - "effectiveStartDate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "adverseActionId": { - "type": "string" - }, - "effectiveLiftDate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "encumbranceType": { - "type": "string" - }, - "clinicalPrivilegeActionCategory": { - "type": "string" - }, - "liftingUser": { - "type": "string" - }, - "dateOfUpdate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - } - } - } + "currentHomeJurisdiction": { + "type": "string", + "description": "The current jurisdiction postal abbreviation if known.", + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy", + "other", + "unknown" + ] }, - "dateOfUpdate": { + "ssnLastFour": { + "pattern": "^[0-9]{4}$", + "type": "string" + }, + "dateOfExpiration": { "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", "format": "date" + }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" + }, + "licenseStatus": { + "type": "string", + "enum": [ + "active", + "inactive" + ] + }, + "familyName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "middleName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "birthMonthDay": { + "pattern": "^[01]{1}[0-9]{1}-[0-3]{1}[0-9]{1}$", + "type": "string", + "format": "date" + }, + "compactConnectRegisteredEmailAddress": { + "maxLength": 100, + "minLength": 5, + "type": "string", + "format": "email" + }, + "dateOfUpdate": { + "type": "string", + "format": "date-time" } } } + } + } + }, + "SandboLicendERazg9NeEsA": { + "required": [ + "status" + ], + "type": "object", + "properties": { + "status": { + "type": "string", + "description": "The status to set the military affiliation to.", + "enum": [ + "inactive" + ] + } + }, + "additionalProperties": false + }, + "SandboLicenwoqrSnm0rsGM": { + "required": [ + "compact", + "dob", + "email", + "familyName", + "givenName", + "jurisdiction", + "licenseType", + "partialSocial", + "token" + ], + "type": "object", + "properties": { + "licenseType": { + "maxLength": 500, + "type": "string", + "description": "Type of license", + "enum": [ + "audiologist", + "speech-language pathologist", + "occupational therapist", + "occupational therapy assistant", + "licensed professional counselor" + ] }, - "ssnLastFour": { - "pattern": "^[0-9]{4}$", - "type": "string" + "compact": { + "maxLength": 100, + "type": "string", + "description": "Compact name" }, - "dateOfExpiration": { + "dob": { "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", - "format": "date" + "description": "Date of birth in YYYY-MM-DD format" }, - "militaryAffiliations": { + "givenName": { + "maxLength": 200, + "type": "string", + "description": "Provider's given name" + }, + "familyName": { + "maxLength": 200, + "type": "string", + "description": "Provider's family name" + }, + "jurisdiction": { + "maxLength": 2, + "minLength": 2, + "type": "string", + "description": "Two-letter jurisdiction code", + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" + ] + }, + "partialSocial": { + "maxLength": 4, + "minLength": 4, + "type": "string", + "description": "Last 4 digits of SSN" + }, + "email": { + "maxLength": 100, + "minLength": 5, + "type": "string", + "description": "Provider's email address", + "format": "email" + }, + "token": { + "type": "string", + "description": "ReCAPTCHA token" + } + } + }, + "SandboLicenaSmGZue6afyn": { + "required": [ + "newEmailAddress" + ], + "type": "object", + "properties": { + "newEmailAddress": { + "maxLength": 100, + "minLength": 5, + "type": "string", + "description": "The new email address to set for the provider", + "format": "email" + } + }, + "additionalProperties": false + }, + "SandboLicenr5TVmpxKfPGq": { + "required": [ + "birthMonthDay", + "compact", + "dateOfExpiration", + "dateOfUpdate", + "familyName", + "givenName", + "licenseJurisdiction", + "licenses", + "militaryAffiliations", + "privilegeJurisdictions", + "privileges", + "providerId", + "type" + ], + "type": "object", + "properties": { + "privileges": { "type": "array", "items": { "required": [ - "affiliationType", + "administratorSetStatus", + "attestations", "compact", + "compactTransactionId", + "dateOfExpiration", + "dateOfIssuance", + "dateOfRenewal", "dateOfUpdate", - "dateOfUpload", - "fileNames", + "history", + "jurisdiction", + "licenseJurisdiction", + "licenseType", + "privilegeId", "providerId", "status", "type" ], "type": "object", "properties": { - "dateOfUpload": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "compact": { + "investigationStatus": { "type": "string", + "description": "Status indicating if the privilege is under investigation", "enum": [ - "aslp", - "octp", - "coun" + "underInvestigation" ] }, - "downloadLinks": { - "type": "array", - "items": { - "required": [ - "fileName", - "url" - ], - "type": "object", - "properties": { - "fileName": { - "type": "string" - }, - "url": { - "type": "string" - } - } - } - }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, - "affiliationType": { + "licenseJurisdiction": { "type": "string", "enum": [ - "militaryMember", - "militaryMemberSpouse" + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" ] }, - "type": { + "compact": { "type": "string", "enum": [ - "militaryAffiliation" + "aslp", + "octp", + "coun" ] }, - "dateOfUpdate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "fileNames": { - "type": "array", - "items": { - "type": "string" - } - }, - "status": { + "jurisdiction": { "type": "string", "enum": [ - "active", - "inactive" - ] - } - } - } - }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, - "licenseStatus": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - }, - "familyName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "middleName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "birthMonthDay": { - "pattern": "^[01]{1}[0-9]{1}-[0-3]{1}[0-9]{1}$", - "type": "string", - "format": "date" - }, - "compactConnectRegisteredEmailAddress": { - "maxLength": 100, - "minLength": 5, - "type": "string", - "format": "email" - }, - "dateOfUpdate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - } - } - }, - "SandboLicenbmJo5pJTGNyk": { - "required": [ - "affiliationType", - "dateOfUpdate", - "dateOfUpload", - "documentUploadFields", - "status" - ], - "type": "object", - "properties": { - "dateOfUpload": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "description": "The date the document was uploaded", - "format": "date" - }, - "affiliationType": { - "type": "string", - "description": "The type of military affiliation", - "enum": [ - "militaryMember", - "militaryMemberSpouse" - ] - }, - "fileNames": { - "type": "array", - "description": "List of military affiliation file names", - "items": { - "type": "string", - "description": "The name of the file being uploaded" - } - }, - "dateOfUpdate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "description": "The date the document was last updated", - "format": "date" - }, - "status": { - "type": "string", - "description": "The status of the military affiliation" - }, - "documentUploadFields": { - "type": "array", - "description": "The fields used to upload documents", - "items": { - "type": "object", - "properties": { - "fields": { - "type": "object", - "additionalProperties": { - "type": "string" - }, - "description": "The form fields used to upload the document" + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" + ] }, - "url": { - "type": "string", - "description": "The url to upload the document to" - } - }, - "description": "The fields used to upload a specific document" - } - } - } - }, - "SandboLicenbvbpTeGqjAcZ": { - "required": [ - "compact", - "providerId", - "recaptchaToken", - "recoveryToken" - ], - "type": "object", - "properties": { - "compact": { - "type": "string", - "description": "Compact abbreviation", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string", - "description": "Provider UUID" - }, - "recaptchaToken": { - "minLength": 1, - "type": "string", - "description": "ReCAPTCHA token for verification" - }, - "recoveryToken": { - "maxLength": 256, - "minLength": 1, - "type": "string", - "description": "Recovery token from the email link" - } - }, - "additionalProperties": false - }, - "SandboLicenBqFE7Lkpte9Z": { - "required": [ - "items", - "pagination" - ], - "type": "object", - "properties": { - "pagination": { - "type": "object", - "properties": { - "prevLastKey": { - "maxLength": 1024, - "minLength": 1, - "type": "object" - }, - "lastKey": { - "maxLength": 1024, - "minLength": 1, - "type": "object" - }, - "pageSize": { - "maximum": 100, - "minimum": 5, - "type": "integer" - } - } - }, - "items": { - "maxLength": 100, - "type": "array", - "items": { - "type": "object", - "oneOf": [ - { - "required": [ - "compactAbbr", - "compactCommissionFee", - "compactName", - "isSandbox", - "paymentProcessorPublicFields", - "transactionFeeConfiguration", - "type" - ], - "type": "object", - "properties": { - "compactCommissionFee": { - "required": [ - "feeAmount", - "feeType" - ], - "type": "object", - "properties": { - "feeAmount": { - "type": "number" - }, - "feeType": { - "type": "string", - "enum": [ - "FLAT_RATE" - ] - } + "attestations": { + "type": "array", + "items": { + "required": [ + "attestationId", + "version" + ], + "type": "object", + "properties": { + "attestationId": { + "maxLength": 100, + "type": "string" + }, + "version": { + "maxLength": 100, + "type": "string" } - }, - "compactAbbr": { - "type": "string", - "description": "The abbreviation of the compact" - }, - "paymentProcessorPublicFields": { - "required": [ - "apiLoginId", - "publicClientKey" - ], - "type": "object", - "properties": { - "publicClientKey": { - "type": "string", - "description": "The public client key for the payment processor" - }, - "apiLoginId": { - "type": "string", - "description": "The API login ID for the payment processor" - } + } + } + }, + "investigations": { + "type": "array", + "items": { + "required": [ + "compact", + "creationDate", + "dateOfUpdate", + "investigationId", + "jurisdiction", + "licenseType", + "providerId", + "submittingUser", + "type" + ], + "type": "object", + "properties": { + "licenseType": { + "type": "string" + }, + "investigationId": { + "type": "string" + }, + "compact": { + "type": "string", + "enum": [ + "aslp", + "octp", + "coun" + ] + }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" + }, + "jurisdiction": { + "type": "string", + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" + ] + }, + "submittingUser": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "investigation" + ] + }, + "creationDate": { + "type": "string", + "format": "date-time" + }, + "dateOfUpdate": { + "type": "string", + "format": "date-time" } - }, - "type": { - "type": "string", - "enum": [ - "compact" - ] - }, - "transactionFeeConfiguration": { - "required": [ - "licenseeCharges" - ], - "type": "object", - "properties": { - "licenseeCharges": { - "required": [ - "active", - "chargeAmount", - "chargeType" - ], - "type": "object", - "properties": { - "chargeType": { - "type": "string", - "description": "The type of transaction fee charge", - "enum": [ - "FLAT_FEE_PER_PRIVILEGE" - ] - }, - "active": { - "type": "boolean", - "description": "Whether the compact is charging licensees transaction fees" - }, - "chargeAmount": { - "type": "number", - "description": "The amount to charge per privilege purchased" + } + } + }, + "history": { + "type": "array", + "items": { + "required": [ + "compact", + "dateOfUpdate", + "jurisdiction", + "previous", + "type", + "updateType" + ], + "type": "object", + "properties": { + "removedValues": { + "type": "array", + "description": "List of field names that were present in the previous record but removed in the update", + "items": { + "type": "string" + } + }, + "licenseType": { + "type": "string", + "enum": [ + "audiologist", + "speech-language pathologist", + "occupational therapist", + "occupational therapy assistant", + "licensed professional counselor" + ] + }, + "compact": { + "type": "string", + "enum": [ + "aslp", + "octp", + "coun" + ] + }, + "previous": { + "required": [ + "administratorSetStatus", + "attestations", + "compactTransactionId", + "dateOfExpiration", + "dateOfIssuance", + "dateOfRenewal", + "dateOfUpdate", + "licenseJurisdiction", + "privilegeId" + ], + "type": "object", + "properties": { + "licenseJurisdiction": { + "type": "string", + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" + ] + }, + "compact": { + "type": "string", + "enum": [ + "aslp", + "octp", + "coun" + ] + }, + "jurisdiction": { + "type": "string", + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" + ] + }, + "attestations": { + "type": "array", + "items": { + "required": [ + "attestationId", + "version" + ], + "type": "object", + "properties": { + "attestationId": { + "maxLength": 100, + "type": "string" + }, + "version": { + "maxLength": 100, + "type": "string" + } + } } + }, + "type": { + "type": "string", + "enum": [ + "privilege" + ] + }, + "compactTransactionId": { + "type": "string" + }, + "dateOfIssuance": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "administratorSetStatus": { + "type": "string", + "enum": [ + "active", + "inactive" + ] + }, + "dateOfExpiration": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "privilegeId": { + "type": "string" + }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" + }, + "dateOfRenewal": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "dateOfUpdate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "enum": [ + "active", + "inactive" + ] } } - } - }, - "isSandbox": { - "type": "boolean", - "description": "Whether the compact is in sandbox mode" - }, - "compactName": { - "type": "string", - "description": "The full name of the compact" - } - } - }, - { - "required": [ - "jurisdictionName", - "jurisprudenceRequirements", - "postalAbbreviation", - "privilegeFees", - "type" - ], - "type": "object", - "properties": { - "privilegeFees": { - "type": "array", - "description": "The fees for the privileges", - "items": { - "required": [ - "amount", - "licenseTypeAbbreviation" - ], + }, + "jurisdiction": { + "type": "string", + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" + ] + }, + "updatedValues": { "type": "object", "properties": { - "amount": { - "type": "number" + "licenseJurisdiction": { + "type": "string", + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" + ] + }, + "compact": { + "type": "string", + "enum": [ + "aslp", + "octp", + "coun" + ] + }, + "jurisdiction": { + "type": "string", + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" + ] + }, + "attestations": { + "type": "array", + "items": { + "required": [ + "attestationId", + "version" + ], + "type": "object", + "properties": { + "attestationId": { + "maxLength": 100, + "type": "string" + }, + "version": { + "maxLength": 100, + "type": "string" + } + } + } }, - "militaryRate": { - "description": "Optional military rate for the privilege fee.", - "oneOf": [ - { - "minimum": 0, - "type": "number" - }, - null + "type": { + "type": "string", + "enum": [ + "privilege" ] }, - "licenseTypeAbbreviation": { + "compactTransactionId": { "type": "string" - } - } - } - }, - "postalAbbreviation": { - "type": "string", - "description": "The postal abbreviation of the jurisdiction" - }, - "jurisprudenceRequirements": { - "required": [ - "required" - ], - "type": "object", - "properties": { - "linkToDocumentation": { - "description": "Optional link to jurisprudence documentation", - "oneOf": [ - { - "type": "string" - }, - null - ] - }, - "required": { - "type": "boolean", - "description": "Whether jurisprudence requirements exist" - } - } - }, - "jurisdictionName": { - "type": "string", - "description": "The name of the jurisdiction" - }, - "type": { - "type": "string", - "enum": [ - "jurisdiction" - ] - } - } - } - ] - } - } - } - }, - "SandboLicens8zWlw8e3VjA": { - "type": "object", - "properties": { - "permissions": { - "type": "object", - "additionalProperties": { - "type": "object", - "properties": { - "actions": { - "type": "object", - "properties": { - "readPrivate": { - "type": "boolean" - }, - "admin": { - "type": "boolean" - }, - "readSSN": { - "type": "boolean" - } - } - }, - "jurisdictions": { - "type": "object", - "additionalProperties": { - "type": "object", - "properties": { - "actions": { - "type": "object", - "properties": { - "readPrivate": { - "type": "boolean" }, - "admin": { - "type": "boolean" + "dateOfIssuance": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" }, - "write": { - "type": "boolean" + "administratorSetStatus": { + "type": "string", + "enum": [ + "active", + "inactive" + ] }, - "readSSN": { - "type": "boolean" - } - }, - "additionalProperties": false - } - } - } - } - }, - "additionalProperties": false - } - } - }, - "additionalProperties": false - }, - "SandboLicenGkLIz61xmgWo": { - "required": [ - "effectiveLiftDate" - ], - "type": "object", - "properties": { - "effectiveLiftDate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "description": "The effective date when the encumbrance will be lifted", - "format": "date" - } - }, - "additionalProperties": false - }, - "SandboLicenQrINPgMnQioZ": { - "type": "object", - "properties": { - "attributes": { - "type": "object", - "properties": { - "givenName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "familyName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "SandboLicenS3OpuzvlfqIR": { - "type": "object", - "properties": { - "context": { - "type": "object", - "properties": { - "userId": { - "maxLength": 100, - "minLength": 1, - "type": "string", - "description": "Optional user ID for feature flag evaluation" - }, - "customAttributes": { - "type": "object", - "additionalProperties": { - "type": "string" - }, - "description": "Optional custom attributes for feature flag evaluation" - } - }, - "additionalProperties": false, - "description": "Optional context for feature flag evaluation" - } - }, - "additionalProperties": false - }, - "SandboLiceno1FF4B6IPNWg": { - "required": [ - "newEmailAddress" - ], - "type": "object", - "properties": { - "newEmailAddress": { - "maxLength": 100, - "minLength": 5, - "type": "string", - "description": "The new email address to set for the provider", - "format": "email" - } - }, - "additionalProperties": false - }, - "SandboLicen15BtNCg0ttcx": { - "required": [ - "attributes", - "permissions" - ], - "type": "object", - "properties": { - "permissions": { - "type": "object", - "additionalProperties": { - "type": "object", - "properties": { - "actions": { - "type": "object", - "properties": { - "readPrivate": { - "type": "boolean" - }, - "admin": { - "type": "boolean" - }, - "readSSN": { - "type": "boolean" - } - } - }, - "jurisdictions": { - "type": "object", - "additionalProperties": { - "type": "object", - "properties": { - "actions": { - "type": "object", - "properties": { - "readPrivate": { - "type": "boolean" + "dateOfExpiration": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "privilegeId": { + "type": "string" + }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" }, - "admin": { - "type": "boolean" + "dateOfRenewal": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" }, - "write": { - "type": "boolean" + "dateOfUpdate": { + "type": "string", + "format": "date-time" }, - "readSSN": { - "type": "boolean" + "status": { + "type": "string", + "enum": [ + "active", + "inactive" + ] } - }, - "additionalProperties": false + } + }, + "type": { + "type": "string", + "enum": [ + "privilegeUpdate" + ] + }, + "dateOfUpdate": { + "type": "string", + "format": "date-time" + }, + "updateType": { + "type": "string", + "enum": [ + "deactivation", + "expiration", + "issuance", + "other", + "renewal", + "encumbrance", + "homeJurisdictionChange", + "registration", + "lifting_encumbrance", + "licenseDeactivation", + "emailChange" + ] } } } - } - }, - "additionalProperties": false - } - }, - "attributes": { - "required": [ - "email", - "familyName", - "givenName" - ], - "type": "object", - "properties": { - "givenName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "familyName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "email": { - "maxLength": 100, - "minLength": 5, - "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "privilege" + ] + }, + "compactTransactionId": { + "type": "string" + }, + "dateOfIssuance": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "licenseType": { + "type": "string", + "enum": [ + "audiologist", + "speech-language pathologist", + "occupational therapist", + "occupational therapy assistant", + "licensed professional counselor" + ] + }, + "administratorSetStatus": { + "type": "string", + "enum": [ + "active", + "inactive" + ] + }, + "dateOfExpiration": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "privilegeId": { + "type": "string" + }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" + }, + "dateOfRenewal": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "adverseActions": { + "type": "array", + "items": { + "required": [ + "actionAgainst", + "adverseActionId", + "compact", + "creationDate", + "dateOfUpdate", + "effectiveStartDate", + "encumbranceType", + "jurisdiction", + "licenseType", + "licenseTypeAbbreviation", + "providerId", + "type" + ], + "type": "object", + "properties": { + "clinicalPrivilegeActionCategories": { + "type": "array", + "description": "The categories of clinical privilege action", + "items": { + "type": "string" + } + }, + "compact": { + "type": "string", + "enum": [ + "aslp", + "octp", + "coun" + ] + }, + "jurisdiction": { + "type": "string", + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" + ] + }, + "licenseTypeAbbreviation": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "adverseAction" + ] + }, + "creationDate": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "actionAgainst": { + "type": "string" + }, + "licenseType": { + "type": "string" + }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" + }, + "effectiveStartDate": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "adverseActionId": { + "type": "string" + }, + "effectiveLiftDate": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "encumbranceType": { + "type": "string" + }, + "clinicalPrivilegeActionCategory": { + "type": "string" + }, + "liftingUser": { + "type": "string" + }, + "dateOfUpdate": { + "type": "string", + "format": "date-time" + } + } + } + }, + "dateOfUpdate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "enum": [ + "active", + "inactive" + ] + } } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "SandboLicenaFsmBhKzUkNI": { - "required": [ - "jurisdiction" - ], - "type": "object", - "properties": { - "jurisdiction": { + } + }, + "licenseJurisdiction": { "type": "string", - "description": "The jurisdiction postal abbreviation to set as home jurisdiction", "enum": [ "al", "ak", @@ -8305,265 +8367,1103 @@ "wa", "wv", "wi", - "wy", - "other" + "wy" ] - } - }, - "additionalProperties": false - }, - "SandboLicenk1ql1I81jsMY": { - "required": [ - "encumbranceEffectiveDate", - "encumbranceType" - ], - "type": "object", - "properties": { - "encumbranceEffectiveDate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + }, + "compact": { "type": "string", - "description": "The effective date of the encumbrance", - "format": "date" + "enum": [ + "aslp", + "octp", + "coun" + ] }, - "clinicalPrivilegeActionCategories": { - "type": "array", - "description": "The categories of clinical privilege action", - "items": { - "type": "string" - } + "npi": { + "pattern": "^[0-9]{10}$", + "type": "string" }, - "encumbranceType": { + "givenName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "compactEligibility": { "type": "string", - "description": "The type of encumbrance", "enum": [ - "fine", - "reprimand", - "required supervision", - "completion of continuing education", - "public reprimand", - "probation", - "injunctive action", - "suspension", - "revocation", - "denial", - "surrender of license", - "modification of previous action-extension", - "modification of previous action-reduction", - "other monitoring", - "other adjudicated action not listed" + "eligible", + "ineligible" ] }, - "clinicalPrivilegeActionCategory": { + "jurisdictionUploadedCompactEligibility": { "type": "string", - "description": "(Deprecated) The category of clinical privilege action. Use clinicalPrivilegeActionCategories instead." - } - }, - "additionalProperties": false - }, - "SandboLicenxSf6anmMiZUl": { - "required": [ - "pagination", - "providers" - ], - "type": "object", - "properties": { - "pagination": { - "type": "object", - "properties": { - "prevLastKey": { - "maxLength": 1024, - "minLength": 1, - "type": "object" - }, - "lastKey": { - "maxLength": 1024, - "minLength": 1, - "type": "object" - }, - "pageSize": { - "maximum": 100, - "minimum": 5, - "type": "integer" - } - } + "enum": [ + "eligible", + "ineligible" + ] }, - "query": { - "type": "object", - "properties": { - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string", - "description": "Internal UUID for the provider" - }, - "jurisdiction": { - "type": "string", - "description": "Filter for providers with privilege/license in a jurisdiction", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "givenName": { - "maxLength": 100, - "type": "string", - "description": "Filter for providers with a given name" - }, - "familyName": { - "maxLength": 100, - "type": "string", - "description": "Filter for providers with a family name" - } + "dateOfBirth": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "jurisdictionUploadedLicenseStatus": { + "type": "string", + "enum": [ + "active", + "inactive" + ] + }, + "privilegeJurisdictions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" + ] } }, - "sorting": { - "required": [ - "key" - ], - "type": "object", - "properties": { - "key": { - "type": "string", - "description": "The key to sort results by", - "enum": [ - "dateOfUpdate", - "familyName" - ] - }, - "direction": { - "type": "string", - "description": "Direction to sort results by", - "enum": [ - "ascending", - "descending" - ] - } - }, - "description": "How to sort results" + "type": { + "type": "string", + "enum": [ + "provider" + ] }, - "providers": { + "suffix": { "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "currentHomeJurisdiction": { + "type": "string", + "description": "The current jurisdiction postal abbreviation if known.", + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy", + "other", + "unknown" + ] + }, + "licenses": { "type": "array", "items": { "required": [ "compact", + "compactEligibility", + "dateOfExpiration", + "dateOfIssuance", + "dateOfRenewal", + "dateOfUpdate", "familyName", "givenName", - "licenseJurisdiction", - "privilegeJurisdictions", + "history", + "homeAddressCity", + "homeAddressPostalCode", + "homeAddressState", + "homeAddressStreet1", + "jurisdiction", + "jurisdictionUploadedCompactEligibility", + "jurisdictionUploadedLicenseStatus", + "licenseStatus", + "licenseType", + "middleName", "providerId", "type" ], "type": "object", "properties": { - "licenseJurisdiction": { + "compact": { + "type": "string", + "enum": [ + "aslp", + "octp", + "coun" + ] + }, + "homeAddressStreet2": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "jurisdiction": { + "type": "string", + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" + ] + }, + "homeAddressStreet1": { + "maxLength": 100, + "minLength": 2, + "type": "string" + }, + "investigations": { + "type": "array", + "items": { + "required": [ + "compact", + "creationDate", + "dateOfUpdate", + "investigationId", + "jurisdiction", + "licenseType", + "providerId", + "submittingUser", + "type" + ], + "type": "object", + "properties": { + "licenseType": { + "type": "string" + }, + "investigationId": { + "type": "string" + }, + "compact": { + "type": "string", + "enum": [ + "aslp", + "octp", + "coun" + ] + }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" + }, + "jurisdiction": { + "type": "string", + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" + ] + }, + "submittingUser": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "investigation" + ] + }, + "creationDate": { + "type": "string", + "format": "date-time" + }, + "dateOfUpdate": { + "type": "string", + "format": "date-time" + } + } + } + }, + "type": { + "type": "string", + "enum": [ + "license-home" + ] + }, + "suffix": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "dateOfIssuance": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "licenseType": { + "type": "string", + "enum": [ + "audiologist", + "speech-language pathologist", + "occupational therapist", + "occupational therapy assistant", + "licensed professional counselor" + ] + }, + "emailAddress": { + "maxLength": 100, + "minLength": 5, + "type": "string", + "format": "email" + }, + "dateOfExpiration": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "homeAddressState": { + "maxLength": 100, + "minLength": 2, + "type": "string" + }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" + }, + "dateOfRenewal": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "familyName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "homeAddressCity": { + "maxLength": 100, + "minLength": 2, + "type": "string" + }, + "licenseNumber": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "investigationStatus": { + "type": "string", + "description": "Status indicating if the license is under investigation", + "enum": [ + "underInvestigation" + ] + }, + "npi": { + "pattern": "^[0-9]{10}$", + "type": "string" + }, + "homeAddressPostalCode": { + "maxLength": 7, + "minLength": 5, + "type": "string" + }, + "compactEligibility": { + "type": "string", + "enum": [ + "eligible", + "ineligible" + ] + }, + "givenName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "jurisdictionUploadedCompactEligibility": { + "type": "string", + "enum": [ + "eligible", + "ineligible" + ] + }, + "dateOfBirth": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "jurisdictionUploadedLicenseStatus": { + "type": "string", + "enum": [ + "active", + "inactive" + ] + }, + "history": { + "type": "array", + "items": { + "required": [ + "compact", + "dateOfUpdate", + "jurisdiction", + "previous", + "type", + "updateType" + ], + "type": "object", + "properties": { + "removedValues": { + "type": "array", + "description": "List of field names that were present in the previous record but removed in the update", + "items": { + "type": "string" + } + }, + "licenseType": { + "type": "string", + "enum": [ + "audiologist", + "speech-language pathologist", + "occupational therapist", + "occupational therapy assistant", + "licensed professional counselor" + ] + }, + "compact": { + "type": "string", + "enum": [ + "aslp", + "octp", + "coun" + ] + }, + "previous": { + "required": [ + "dateOfExpiration", + "dateOfIssuance", + "dateOfRenewal", + "familyName", + "givenName", + "homeAddressCity", + "homeAddressPostalCode", + "homeAddressState", + "homeAddressStreet1", + "jurisdictionUploadedCompactEligibility", + "jurisdictionUploadedLicenseStatus", + "middleName" + ], + "type": "object", + "properties": { + "homeAddressStreet2": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "npi": { + "pattern": "^[0-9]{10}$", + "type": "string" + }, + "homeAddressPostalCode": { + "maxLength": 7, + "minLength": 5, + "type": "string" + }, + "givenName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "homeAddressStreet1": { + "maxLength": 100, + "minLength": 2, + "type": "string" + }, + "compactEligibility": { + "type": "string", + "enum": [ + "eligible", + "ineligible" + ] + }, + "jurisdictionUploadedCompactEligibility": { + "type": "string", + "enum": [ + "eligible", + "ineligible" + ] + }, + "dateOfBirth": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "jurisdictionUploadedLicenseStatus": { + "type": "string", + "enum": [ + "active", + "inactive" + ] + }, + "suffix": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "dateOfIssuance": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "emailAddress": { + "maxLength": 100, + "minLength": 5, + "type": "string", + "format": "email" + }, + "dateOfExpiration": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "phoneNumber": { + "pattern": "^\\+[0-9]{8,15}$", + "type": "string" + }, + "homeAddressState": { + "maxLength": 100, + "minLength": 2, + "type": "string" + }, + "dateOfRenewal": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "licenseStatus": { + "type": "string", + "enum": [ + "active", + "inactive" + ] + }, + "familyName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "homeAddressCity": { + "maxLength": 100, + "minLength": 2, + "type": "string" + }, + "licenseNumber": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "middleName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "licenseStatusName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + } + } + }, + "jurisdiction": { + "type": "string", + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" + ] + }, + "updatedValues": { + "type": "object", + "properties": { + "homeAddressStreet2": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "npi": { + "pattern": "^[0-9]{10}$", + "type": "string" + }, + "homeAddressPostalCode": { + "maxLength": 7, + "minLength": 5, + "type": "string" + }, + "givenName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "homeAddressStreet1": { + "maxLength": 100, + "minLength": 2, + "type": "string" + }, + "compactEligibility": { + "type": "string", + "enum": [ + "eligible", + "ineligible" + ] + }, + "jurisdictionUploadedCompactEligibility": { + "type": "string", + "enum": [ + "eligible", + "ineligible" + ] + }, + "dateOfBirth": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "jurisdictionUploadedLicenseStatus": { + "type": "string", + "enum": [ + "active", + "inactive" + ] + }, + "suffix": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "dateOfIssuance": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "emailAddress": { + "maxLength": 100, + "minLength": 5, + "type": "string", + "format": "email" + }, + "dateOfExpiration": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "phoneNumber": { + "pattern": "^\\+[0-9]{8,15}$", + "type": "string" + }, + "homeAddressState": { + "maxLength": 100, + "minLength": 2, + "type": "string" + }, + "dateOfRenewal": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "licenseStatus": { + "type": "string", + "enum": [ + "active", + "inactive" + ] + }, + "familyName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "homeAddressCity": { + "maxLength": 100, + "minLength": 2, + "type": "string" + }, + "licenseNumber": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "middleName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "licenseStatusName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + } + } + }, + "type": { + "type": "string", + "enum": [ + "licenseUpdate" + ] + }, + "dateOfUpdate": { + "type": "string", + "format": "date-time" + }, + "updateType": { + "type": "string", + "enum": [ + "deactivation", + "expiration", + "issuance", + "other", + "renewal", + "encumbrance", + "homeJurisdictionChange", + "registration", + "lifting_encumbrance", + "licenseDeactivation", + "emailChange" + ] + } + } + } + }, + "ssnLastFour": { + "pattern": "^[0-9]{4}$", + "type": "string" + }, + "phoneNumber": { + "pattern": "^\\+[0-9]{8,15}$", + "type": "string" + }, + "licenseStatus": { "type": "string", "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" + "active", + "inactive" ] }, + "middleName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "licenseStatusName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "adverseActions": { + "type": "array", + "items": { + "required": [ + "actionAgainst", + "adverseActionId", + "compact", + "creationDate", + "dateOfUpdate", + "effectiveStartDate", + "encumbranceType", + "jurisdiction", + "licenseType", + "licenseTypeAbbreviation", + "providerId", + "type" + ], + "type": "object", + "properties": { + "clinicalPrivilegeActionCategories": { + "type": "array", + "description": "The categories of clinical privilege action", + "items": { + "type": "string" + } + }, + "compact": { + "type": "string", + "enum": [ + "aslp", + "octp", + "coun" + ] + }, + "jurisdiction": { + "type": "string", + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" + ] + }, + "licenseTypeAbbreviation": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "adverseAction" + ] + }, + "creationDate": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "actionAgainst": { + "type": "string" + }, + "licenseType": { + "type": "string" + }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" + }, + "effectiveStartDate": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "adverseActionId": { + "type": "string" + }, + "effectiveLiftDate": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "encumbranceType": { + "type": "string" + }, + "clinicalPrivilegeActionCategory": { + "type": "string" + }, + "liftingUser": { + "type": "string" + }, + "dateOfUpdate": { + "type": "string", + "format": "date-time" + } + } + } + }, + "dateOfUpdate": { + "type": "string", + "format": "date-time" + } + } + } + }, + "ssnLastFour": { + "pattern": "^[0-9]{4}$", + "type": "string" + }, + "dateOfExpiration": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "militaryAffiliations": { + "type": "array", + "items": { + "required": [ + "affiliationType", + "compact", + "dateOfUpdate", + "dateOfUpload", + "fileNames", + "providerId", + "status", + "type" + ], + "type": "object", + "properties": { + "dateOfUpload": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, "compact": { "type": "string", "enum": [ @@ -8572,169 +9472,96 @@ "coun" ] }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, - "givenName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "familyName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "middleName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "privilegeJurisdictions": { + "downloadLinks": { "type": "array", "items": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] + "required": [ + "fileName", + "url" + ], + "type": "object", + "properties": { + "fileName": { + "type": "string" + }, + "url": { + "type": "string" + } + } } }, - "type": { + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" + }, + "affiliationType": { "type": "string", "enum": [ - "provider" + "militaryMember", + "militaryMemberSpouse" ] }, - "suffix": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "currentHomeJurisdiction": { + "type": { "type": "string", - "description": "The current jurisdiction postal abbreviation if known.", "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy", - "other", - "unknown" + "militaryAffiliation" ] }, "dateOfUpdate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", - "format": "date" + "format": "date-time" + }, + "fileNames": { + "type": "array", + "items": { + "type": "string" + } + }, + "status": { + "type": "string", + "enum": [ + "active", + "inactive" + ] } } } + }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" + }, + "licenseStatus": { + "type": "string", + "enum": [ + "active", + "inactive" + ] + }, + "familyName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "middleName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "birthMonthDay": { + "pattern": "^[01]{1}[0-9]{1}-[0-3]{1}[0-9]{1}$", + "type": "string", + "format": "date" + }, + "compactConnectRegisteredEmailAddress": { + "maxLength": 100, + "minLength": 5, + "type": "string", + "format": "email" + }, + "dateOfUpdate": { + "type": "string", + "format": "date-time" } } } diff --git a/backend/compact-connect/docs/internal/postman/postman-collection.json b/backend/compact-connect/docs/internal/postman/postman-collection.json index 18f4c2200..5c35dd33a 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": "5e637b8e-9328-4620-a612-6b617605b439", + "_postman_id": "7cb454e4-7ab1-426a-9dda-b7b0ed228704", "description": { "content": "", "type": "text/plain" @@ -401,7 +401,7 @@ "item": [ { "event": [], - "id": "23793feb-0c0d-41c1-803d-946d805d4574", + "id": "c1b1d849-13b2-4f67-baac-6234dcd7b77c", "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\": \"ky\"\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}", + "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}", "code": 200, "cookie": [], "header": [ @@ -453,7 +453,7 @@ "value": "application/json" } ], - "id": "6095477c-0639-4313-b1f6-560ee5efc01d", + "id": "3cd40a5c-9778-49c5-ac94-0a9bc9d477c5", "name": "200 response", "originalRequest": { "body": {}, @@ -491,7 +491,7 @@ }, { "event": [], - "id": "78d24e49-cd10-4fcd-b463-37c5bec5f831", + "id": "3568af2c-3a8b-4a0a-992a-f90b1f5b6685", "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\": \"il\"\n },\n {\n \"isLive\": \"\",\n \"postalAbbreviation\": \"nh\"\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\": \"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}" }, "description": {}, "header": [ @@ -556,7 +556,7 @@ "value": "application/json" } ], - "id": "b3b7d273-4ba7-4787-a25d-9a50858b034e", + "id": "f50a3dc3-9641-4ae0-8050-d33c19c7970e", "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\": \"il\"\n },\n {\n \"isLive\": \"\",\n \"postalAbbreviation\": \"nh\"\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\": \"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}" }, "header": [ { @@ -613,7 +613,7 @@ "item": [ { "event": [], - "id": "ce36eb0f-c26e-452e-88b9-26a80cefe710", + "id": "e1d7d1b3-0caa-44e1-aab8-0b0d9520f54d", "name": "/v1/compacts/:compact/attestations/:attestationId", "protocolProfileBehavior": { "disableBodyPruning": true @@ -677,7 +677,7 @@ "value": "application/json" } ], - "id": "6d805ffc-3f03-429a-a2ec-7cc414c9b09b", + "id": "c61b5da2-6143-456f-80ba-71cd6300bbcd", "name": "200 response", "originalRequest": { "body": {}, @@ -729,7 +729,7 @@ "item": [ { "event": [], - "id": "2cb2a4bc-7ee8-43ad-8bf1-50b569eab181", + "id": "784d7a36-d743-4499-83a8-9a35635bd4f7", "name": "/v1/compacts/:compact/credentials/payment-processor", "protocolProfileBehavior": { "disableBodyPruning": true @@ -796,7 +796,7 @@ "value": "application/json" } ], - "id": "02e53863-4227-4884-a561-1a201b75a1f2", + "id": "49cc093e-9b56-4bef-9ade-555b3398672b", "name": "200 response", "originalRequest": { "body": { @@ -858,7 +858,7 @@ "item": [ { "event": [], - "id": "4e7c5159-3dfe-4d3e-a962-8eb9036e45c3", + "id": "edd82d48-8d82-4b01-9b7a-248727deabee", "name": "/v1/compacts/:compact/jurisdictions", "protocolProfileBehavior": { "disableBodyPruning": true @@ -911,7 +911,7 @@ "value": "application/json" } ], - "id": "a788b8aa-efa2-45b9-ae00-22a5606fa311", + "id": "a16b33dd-17fe-4be5-91da-8bbc43fbe565", "name": "200 response", "originalRequest": { "body": {}, @@ -984,7 +984,7 @@ } } ], - "id": "e8ae53d0-e6bd-4137-aedc-8b9cf4e410a1", + "id": "19fef01d-8597-415b-80d2-16cc5c6b328f", "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 \"officia_f\": \"\"\n },\n \"url\": \"\"\n }\n}", + "body": "{\n \"upload\": {\n \"fields\": {\n \"key_0\": \"\"\n },\n \"url\": \"\"\n }\n}", "code": 200, "cookie": [], "header": [ @@ -1050,7 +1050,7 @@ "value": "application/json" } ], - "id": "82717e1a-3dad-4509-87f4-bf58add92bbd", + "id": "02ec708c-bdb8-4064-b7fd-37a790997853", "name": "200 response", "originalRequest": { "body": {}, @@ -1110,7 +1110,7 @@ "item": [ { "event": [], - "id": "6853ddc2-310c-480a-88c2-3c60e1ad2fbf", + "id": "2974d272-c532-41ce-91cc-90f24da34f00", "name": "/v1/compacts/:compact/providers/query", "protocolProfileBehavior": { "disableBodyPruning": true @@ -1124,7 +1124,7 @@ "language": "json" } }, - "raw": "{\n \"query\": {\n \"providerId\": \"3f3c1b6f-4d02-4048-adef-7a8ec269307b\",\n \"jurisdiction\": \"co\",\n \"givenName\": \"\",\n \"familyName\": \"\"\n },\n \"pagination\": {\n \"lastKey\": \"\",\n \"pageSize\": \"\"\n },\n \"sorting\": {\n \"key\": \"dateOfUpdate\",\n \"direction\": \"descending\"\n }\n}" + "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}" }, "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-15\",\n \"compact\": \"octp\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"2019-12-29\",\n \"dateOfUpdate\": \"1509-09-03\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"licenseJurisdiction\": \"ms\",\n \"licenseStatus\": \"active\",\n \"privilegeJurisdictions\": [\n \"tn\",\n \"nd\"\n ],\n \"providerId\": \"63ce33be-9b01-4615-a8e5-be23ae32ac24\",\n \"type\": \"provider\",\n \"npi\": \"6210275723\",\n \"dateOfBirth\": \"1208-10-14\",\n \"suffix\": \"\",\n \"currentHomeJurisdiction\": \"unknown\",\n \"ssnLastFour\": \"3597\",\n \"middleName\": \"\",\n \"compactConnectRegisteredEmailAddress\": \"\"\n },\n {\n \"birthMonthDay\": \"10-05\",\n \"compact\": \"aslp\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"2818-12-29\",\n \"dateOfUpdate\": \"2767-05-31\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"licenseJurisdiction\": \"nm\",\n \"licenseStatus\": \"active\",\n \"privilegeJurisdictions\": [\n \"wa\",\n \"pr\"\n ],\n \"providerId\": \"7a2f41b7-d0fe-4c58-9669-6808e0eee745\",\n \"type\": \"provider\",\n \"npi\": \"1896325843\",\n \"dateOfBirth\": \"2934-11-31\",\n \"suffix\": \"\",\n \"currentHomeJurisdiction\": \"nj\",\n \"ssnLastFour\": \"9104\",\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\": \"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}", "code": 200, "cookie": [], "header": [ @@ -1177,7 +1177,7 @@ "value": "application/json" } ], - "id": "e53980a8-1143-4024-bb86-7f157fc3c354", + "id": "3c87e5d0-6363-4641-8a9a-681c6a5e6f21", "name": "200 response", "originalRequest": { "body": { @@ -1188,7 +1188,7 @@ "language": "json" } }, - "raw": "{\n \"query\": {\n \"providerId\": \"3f3c1b6f-4d02-4048-adef-7a8ec269307b\",\n \"jurisdiction\": \"co\",\n \"givenName\": \"\",\n \"familyName\": \"\"\n },\n \"pagination\": {\n \"lastKey\": \"\",\n \"pageSize\": \"\"\n },\n \"sorting\": {\n \"key\": \"dateOfUpdate\",\n \"direction\": \"descending\"\n }\n}" + "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}" }, "header": [ { @@ -1236,7 +1236,7 @@ "item": [ { "event": [], - "id": "14737019-8659-4b74-930e-ce7a28ffcacc", + "id": "afbfff17-6d80-4e06-bd96-42ab7d331349", "name": "/v1/compacts/:compact/providers/:providerId", "protocolProfileBehavior": { "disableBodyPruning": true @@ -1291,7 +1291,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"birthMonthDay\": \"10-13\",\n \"compact\": \"octp\",\n \"dateOfExpiration\": \"2708-12-19\",\n \"dateOfUpdate\": \"2235-11-07\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"licenseJurisdiction\": \"or\",\n \"licenses\": [\n {\n \"compact\": \"octp\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"1194-11-07\",\n \"dateOfIssuance\": \"1811-01-06\",\n \"dateOfRenewal\": \"1005-01-30\",\n \"dateOfUpdate\": \"1228-10-17\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"history\": [\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"1122-08-31\",\n \"jurisdiction\": \"de\",\n \"previous\": {\n \"dateOfExpiration\": \"2825-06-30\",\n \"dateOfIssuance\": \"2552-11-09\",\n \"dateOfRenewal\": \"1554-03-08\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"7904266478\",\n \"compactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1130-10-09\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+127199520822501\",\n \"licenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"expiration\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"audiologist\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"npi\": \"7996207438\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"2689-12-23\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2349-01-07\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"1981-10-31\",\n \"phoneNumber\": \"+48186155665\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"2985-03-02\",\n \"licenseStatus\": \"active\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n },\n {\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"2799-03-17\",\n \"jurisdiction\": \"pa\",\n \"previous\": {\n \"dateOfExpiration\": \"1712-01-28\",\n \"dateOfIssuance\": \"2124-05-31\",\n \"dateOfRenewal\": \"1804-12-25\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"3975501387\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"1678-08-23\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+80432649\",\n \"licenseStatus\": \"inactive\",\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\": \"5745410699\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"2459-01-20\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2074-05-31\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"1606-04-30\",\n \"phoneNumber\": \"+38094519029451\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"2402-11-03\",\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\": \"mo\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"audiologist\",\n \"middleName\": \"\",\n \"providerId\": \"d6a677d3-d7fb-4780-9aa5-0b54def3f5fb\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"licenseNumber\": \"\",\n \"npi\": \"6162231934\",\n \"dateOfBirth\": \"2116-11-23\",\n \"ssnLastFour\": \"2827\",\n \"phoneNumber\": \"+316315679209\",\n \"licenseStatusName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"2958-09-30\",\n \"dateOfUpdate\": \"1010-09-30\",\n \"effectiveStartDate\": \"2967-11-11\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"md\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"92cf3f74-4eba-4e03-8960-fe8244789ade\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2333-10-23\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"2829-11-23\",\n \"dateOfUpdate\": \"1986-11-31\",\n \"effectiveStartDate\": \"1863-08-02\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"ca\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"3ebb12e1-61e9-4764-bc27-deb0c708968b\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2426-05-14\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n }\n ]\n },\n {\n \"compact\": \"coun\",\n \"compactEligibility\": \"eligible\",\n \"dateOfExpiration\": \"2303-05-13\",\n \"dateOfIssuance\": \"1600-02-08\",\n \"dateOfRenewal\": \"1136-11-23\",\n \"dateOfUpdate\": \"2758-11-26\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"history\": [\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"2228-10-08\",\n \"jurisdiction\": \"or\",\n \"previous\": {\n \"dateOfExpiration\": \"1909-03-15\",\n \"dateOfIssuance\": \"1920-12-19\",\n \"dateOfRenewal\": \"1820-11-05\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"4309547090\",\n \"compactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2575-01-06\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+282921155\",\n \"licenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"encumbrance\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"audiologist\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"npi\": \"1272654411\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"2191-12-26\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2166-09-22\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"2233-10-31\",\n \"phoneNumber\": \"+83080416937936\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"1714-09-03\",\n \"licenseStatus\": \"inactive\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n },\n {\n \"compact\": \"aslp\",\n \"dateOfUpdate\": \"1525-12-30\",\n \"jurisdiction\": \"ca\",\n \"previous\": {\n \"dateOfExpiration\": \"1149-10-31\",\n \"dateOfIssuance\": \"1206-01-04\",\n \"dateOfRenewal\": \"2125-10-28\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"7139133672\",\n \"compactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2503-11-06\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+22157243484\",\n \"licenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"issuance\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"occupational therapist\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"npi\": \"5902669871\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"1846-06-16\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"1530-10-31\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"2630-10-29\",\n \"phoneNumber\": \"+463314989084\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"2898-04-19\",\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\": \"ri\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"occupational therapy assistant\",\n \"middleName\": \"\",\n \"providerId\": \"90bf4c45-b2bc-4e1c-9055-23adc019b9ff\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"licenseNumber\": \"\",\n \"npi\": \"6796883381\",\n \"dateOfBirth\": \"2836-10-24\",\n \"ssnLastFour\": \"3288\",\n \"phoneNumber\": \"+58540754234\",\n \"licenseStatusName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"aslp\",\n \"creationDate\": \"1925-08-08\",\n \"dateOfUpdate\": \"2975-04-19\",\n \"effectiveStartDate\": \"2776-11-04\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"tn\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"17990d99-1def-4abd-8a8a-3122ca3219da\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2663-04-30\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"octp\",\n \"creationDate\": \"1285-04-03\",\n \"dateOfUpdate\": \"2856-10-30\",\n \"effectiveStartDate\": \"2544-08-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"ky\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"2b1d1723-dfb8-40bc-90cf-902c703548c9\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1523-10-10\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"militaryAffiliations\": [\n {\n \"affiliationType\": \"militaryMemberSpouse\",\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"2565-05-30\",\n \"dateOfUpload\": \"2261-11-03\",\n \"fileNames\": [\n \"\",\n \"\"\n ],\n \"providerId\": \"84e50f08-03c5-4137-b371-2550b5369cdb\",\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\": \"2471-04-28\",\n \"dateOfUpload\": \"2015-12-30\",\n \"fileNames\": [\n \"\",\n \"\"\n ],\n \"providerId\": \"6850e115-8f7f-4f2e-bb91-d0710a6fc6c7\",\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 \"il\",\n \"wa\"\n ],\n \"privileges\": [\n {\n \"administratorSetStatus\": \"inactive\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compact\": \"aslp\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1403-11-30\",\n \"dateOfIssuance\": \"2058-06-04\",\n \"dateOfRenewal\": \"2848-04-31\",\n \"dateOfUpdate\": \"2721-10-29\",\n \"history\": [\n {\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"2232-10-08\",\n \"jurisdiction\": \"hi\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"2380-07-31\",\n \"dateOfIssuance\": \"1675-11-01\",\n \"dateOfRenewal\": \"1533-01-06\",\n \"dateOfUpdate\": \"1664-10-31\",\n \"licenseJurisdiction\": \"dc\",\n \"privilegeId\": \"\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"oh\",\n \"type\": \"privilege\",\n \"providerId\": \"f215ffd6-257c-40c5-8daa-9a21135d1b03\",\n \"status\": \"inactive\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"lifting_encumbrance\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"occupational therapist\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"id\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"sc\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"2178-07-01\",\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"2128-01-07\",\n \"privilegeId\": \"\",\n \"providerId\": \"11b11933-f36a-4eb7-83a0-542011f49873\",\n \"dateOfRenewal\": \"2696-05-20\",\n \"dateOfUpdate\": \"1259-03-06\",\n \"status\": \"inactive\"\n }\n },\n {\n \"compact\": \"aslp\",\n \"dateOfUpdate\": \"1899-11-28\",\n \"jurisdiction\": \"id\",\n \"previous\": {\n \"administratorSetStatus\": \"active\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1808-12-25\",\n \"dateOfIssuance\": \"1867-04-31\",\n \"dateOfRenewal\": \"1948-03-09\",\n \"dateOfUpdate\": \"1544-11-09\",\n \"licenseJurisdiction\": \"pr\",\n \"privilegeId\": \"\",\n \"compact\": \"coun\",\n \"jurisdiction\": \"ak\",\n \"type\": \"privilege\",\n \"providerId\": \"98e3f59c-0fbc-4aa4-bce1-7d5689f18812\",\n \"status\": \"inactive\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"renewal\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"occupational therapist\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"wv\",\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\": \"1221-02-30\",\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"2814-11-15\",\n \"privilegeId\": \"\",\n \"providerId\": \"532d563d-a1bc-40dc-a5f0-8a0a86f05451\",\n \"dateOfRenewal\": \"1885-12-31\",\n \"dateOfUpdate\": \"1384-02-11\",\n \"status\": \"active\"\n }\n }\n ],\n \"jurisdiction\": \"vt\",\n \"licenseJurisdiction\": \"mn\",\n \"licenseType\": \"occupational therapy assistant\",\n \"privilegeId\": \"\",\n \"providerId\": \"4b524f8c-6a30-4ee6-ab6c-55c37a68167f\",\n \"status\": \"inactive\",\n \"type\": \"privilege\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"octp\",\n \"creationDate\": \"2986-01-02\",\n \"dateOfUpdate\": \"1994-10-13\",\n \"effectiveStartDate\": \"1242-11-28\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"sc\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"bbf662ac-7e91-4140-903c-0c350d7dc5f7\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2961-12-04\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"octp\",\n \"creationDate\": \"2886-12-31\",\n \"dateOfUpdate\": \"2098-11-30\",\n \"effectiveStartDate\": \"1188-03-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"me\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"7a3ab2b7-4041-47eb-94c4-e50ec792a958\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2457-03-28\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n }\n ]\n },\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\": \"1412-03-31\",\n \"dateOfIssuance\": \"1750-01-08\",\n \"dateOfRenewal\": \"2654-02-08\",\n \"dateOfUpdate\": \"1882-06-30\",\n \"history\": [\n {\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"1696-11-03\",\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\": \"2747-09-30\",\n \"dateOfIssuance\": \"2819-02-21\",\n \"dateOfRenewal\": \"1924-12-14\",\n \"dateOfUpdate\": \"2059-01-31\",\n \"licenseJurisdiction\": \"pr\",\n \"privilegeId\": \"\",\n \"compact\": \"coun\",\n \"jurisdiction\": \"ks\",\n \"type\": \"privilege\",\n \"providerId\": \"81e91006-0dfe-4322-8a41-b7d78aae5f69\",\n \"status\": \"active\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"licenseDeactivation\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"speech-language pathologist\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"az\",\n \"compact\": \"coun\",\n \"jurisdiction\": \"sc\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"1608-08-04\",\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"2931-11-31\",\n \"privilegeId\": \"\",\n \"providerId\": \"c4cc5de7-00ec-43e9-97f0-5dab5c0ae1a6\",\n \"dateOfRenewal\": \"1003-10-19\",\n \"dateOfUpdate\": \"1809-12-04\",\n \"status\": \"inactive\"\n }\n },\n {\n \"compact\": \"aslp\",\n \"dateOfUpdate\": \"1973-11-16\",\n \"jurisdiction\": \"ia\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1210-10-31\",\n \"dateOfIssuance\": \"1470-12-02\",\n \"dateOfRenewal\": \"2582-01-31\",\n \"dateOfUpdate\": \"1348-05-14\",\n \"licenseJurisdiction\": \"ks\",\n \"privilegeId\": \"\",\n \"compact\": \"coun\",\n \"jurisdiction\": \"ok\",\n \"type\": \"privilege\",\n \"providerId\": \"e364f6bd-3d30-4881-ad2e-99d25dd13612\",\n \"status\": \"inactive\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"emailChange\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"speech-language pathologist\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"md\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"me\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"1321-02-30\",\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"1415-12-13\",\n \"privilegeId\": \"\",\n \"providerId\": \"2550a822-a6a2-46f5-9d6f-381ad5c7ad50\",\n \"dateOfRenewal\": \"1441-10-20\",\n \"dateOfUpdate\": \"2823-01-02\",\n \"status\": \"active\"\n }\n }\n ],\n \"jurisdiction\": \"ky\",\n \"licenseJurisdiction\": \"ne\",\n \"licenseType\": \"speech-language pathologist\",\n \"privilegeId\": \"\",\n \"providerId\": \"e7d03cad-55f3-4a69-bf06-109772196128\",\n \"status\": \"inactive\",\n \"type\": \"privilege\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"1836-11-03\",\n \"dateOfUpdate\": \"1386-07-12\",\n \"effectiveStartDate\": \"2383-04-14\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"ar\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"91d01ece-89f1-46d0-8105-52a9517f6a70\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1297-12-28\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"1188-10-30\",\n \"dateOfUpdate\": \"2977-10-31\",\n \"effectiveStartDate\": \"2846-08-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"fl\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"5205a6c0-1147-4655-a17b-ff3780c163d9\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1879-11-24\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"providerId\": \"94022a97-0d0d-4153-bda8-a15afc713ee2\",\n \"type\": \"provider\",\n \"npi\": \"8197107476\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2021-12-30\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"suffix\": \"\",\n \"currentHomeJurisdiction\": \"sc\",\n \"ssnLastFour\": \"2592\",\n \"licenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"compactConnectRegisteredEmailAddress\": \"\"\n}", + "body": "{\n \"birthMonthDay\": \"08-15\",\n \"compact\": \"octp\",\n \"dateOfExpiration\": \"2385-12-29\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"licenseJurisdiction\": \"nc\",\n \"licenses\": [\n {\n \"compact\": \"octp\",\n \"compactEligibility\": \"eligible\",\n \"dateOfExpiration\": \"2936-05-05\",\n \"dateOfIssuance\": \"2254-10-17\",\n \"dateOfRenewal\": \"2835-10-30\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"history\": [\n {\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"md\",\n \"previous\": {\n \"dateOfExpiration\": \"1800-04-06\",\n \"dateOfIssuance\": \"2972-08-07\",\n \"dateOfRenewal\": \"2438-09-14\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"6466950304\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"2691-05-23\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+855527173535\",\n \"licenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"deactivation\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"occupational therapist\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"npi\": \"8394405807\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"eligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1842-07-31\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2300-08-05\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"2863-08-03\",\n \"phoneNumber\": \"+3528187300\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"2856-05-08\",\n \"licenseStatus\": \"inactive\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n },\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"mt\",\n \"previous\": {\n \"dateOfExpiration\": \"1918-10-05\",\n \"dateOfIssuance\": \"1476-12-15\",\n \"dateOfRenewal\": \"1145-11-31\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"0596273100\",\n \"compactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1028-12-30\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+9136425909\",\n \"licenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"lifting_encumbrance\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"speech-language pathologist\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"npi\": \"2808925314\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"1326-05-06\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2399-06-25\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"2473-11-30\",\n \"phoneNumber\": \"+1013464869\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"2257-10-20\",\n \"licenseStatus\": \"active\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n }\n ],\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdiction\": \"hi\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"licenseStatus\": \"inactive\",\n \"licenseType\": \"occupational therapy assistant\",\n \"middleName\": \"\",\n \"providerId\": \"9b0bf21b-92f8-41d9-9df4-eaabd84adeee\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"investigations\": [\n {\n \"compact\": \"coun\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"ut\",\n \"licenseType\": \"\",\n \"providerId\": \"ad1db718-9d5c-4842-8b45-9b34ee4e0cca\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"octp\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"vt\",\n \"licenseType\": \"\",\n \"providerId\": \"844db6f5-44af-4fc9-9221-3015ed96cbef\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"licenseNumber\": \"\",\n \"investigationStatus\": \"underInvestigation\",\n \"npi\": \"2619765297\",\n \"dateOfBirth\": \"1768-09-08\",\n \"ssnLastFour\": \"4600\",\n \"phoneNumber\": \"+59112126684\",\n \"licenseStatusName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"octp\",\n \"creationDate\": \"1777-09-23\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2708-03-02\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"ms\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"aeca0267-7733-48d0-a71c-d884e4754f14\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2396-01-09\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"aslp\",\n \"creationDate\": \"2958-05-22\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2261-06-31\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"mt\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"ba21be91-43db-4c7a-98de-607d32eb34b6\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1185-12-14\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n }\n ]\n },\n {\n \"compact\": \"aslp\",\n \"compactEligibility\": \"eligible\",\n \"dateOfExpiration\": \"1169-12-27\",\n \"dateOfIssuance\": \"2302-07-11\",\n \"dateOfRenewal\": \"1624-03-08\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"history\": [\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"hi\",\n \"previous\": {\n \"dateOfExpiration\": \"2804-01-30\",\n \"dateOfIssuance\": \"1778-03-30\",\n \"dateOfRenewal\": \"1452-12-01\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"8839374275\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"2408-12-29\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+25666997428056\",\n \"licenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"homeJurisdictionChange\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"speech-language pathologist\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"npi\": \"3527141483\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"eligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1539-11-04\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2033-10-28\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"2946-02-27\",\n \"phoneNumber\": \"+11739899598\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"1036-10-30\",\n \"licenseStatus\": \"active\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n },\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"dc\",\n \"previous\": {\n \"dateOfExpiration\": \"2132-01-04\",\n \"dateOfIssuance\": \"2948-11-30\",\n \"dateOfRenewal\": \"1580-10-30\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"3443777389\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"2208-08-30\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+69171972\",\n \"licenseStatus\": \"active\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"deactivation\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"speech-language pathologist\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"npi\": \"2199245845\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"eligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2289-12-31\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2595-12-09\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"2635-11-09\",\n \"phoneNumber\": \"+538742548588950\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"1656-08-16\",\n \"licenseStatus\": \"active\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n }\n ],\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdiction\": \"ky\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"occupational therapist\",\n \"middleName\": \"\",\n \"providerId\": \"481ba68e-ab28-427f-befb-fba6bf3b3bae\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"investigations\": [\n {\n \"compact\": \"aslp\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"wa\",\n \"licenseType\": \"\",\n \"providerId\": \"f1c029aa-dae1-4ce3-a43c-1bf2320b7642\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"aslp\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"wv\",\n \"licenseType\": \"\",\n \"providerId\": \"8c92261b-fea9-470a-8fe8-dcaca7ca8697\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"licenseNumber\": \"\",\n \"investigationStatus\": \"underInvestigation\",\n \"npi\": \"1690493112\",\n \"dateOfBirth\": \"1751-01-02\",\n \"ssnLastFour\": \"8079\",\n \"phoneNumber\": \"+106891529\",\n \"licenseStatusName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"1394-05-20\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1355-11-25\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"mn\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"6146bf62-a9f2-4c96-9d67-8c32cf05de02\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1150-11-05\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"2103-02-30\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2104-10-21\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"al\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"bd5c9993-b19c-49a9-a241-6b67768bb1a5\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1567-09-31\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"militaryAffiliations\": [\n {\n \"affiliationType\": \"militaryMember\",\n \"compact\": \"aslp\",\n \"dateOfUpdate\": \"\",\n \"dateOfUpload\": \"2814-11-05\",\n \"fileNames\": [\n \"\",\n \"\"\n ],\n \"providerId\": \"a6dad0d8-c0c7-4274-9d1f-a530a54209d2\",\n \"status\": \"active\",\n \"type\": \"militaryAffiliation\",\n \"downloadLinks\": [\n {\n \"fileName\": \"\",\n \"url\": \"\"\n },\n {\n \"fileName\": \"\",\n \"url\": \"\"\n }\n ]\n },\n {\n \"affiliationType\": \"militaryMemberSpouse\",\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"dateOfUpload\": \"2214-04-11\",\n \"fileNames\": [\n \"\",\n \"\"\n ],\n \"providerId\": \"ca2d993d-8064-4a9a-9db0-f22f39e37118\",\n \"status\": \"active\",\n \"type\": \"militaryAffiliation\",\n \"downloadLinks\": [\n {\n \"fileName\": \"\",\n \"url\": \"\"\n },\n {\n \"fileName\": \"\",\n \"url\": \"\"\n }\n ]\n }\n ],\n \"privilegeJurisdictions\": [\n \"az\",\n \"ga\"\n ],\n \"privileges\": [\n {\n \"administratorSetStatus\": \"active\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compact\": \"coun\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1994-11-05\",\n \"dateOfIssuance\": \"2425-09-15\",\n \"dateOfRenewal\": \"1310-11-08\",\n \"dateOfUpdate\": \"\",\n \"history\": [\n {\n \"compact\": \"aslp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"ms\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"2993-03-27\",\n \"dateOfIssuance\": \"2072-10-31\",\n \"dateOfRenewal\": \"2114-11-25\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"mn\",\n \"privilegeId\": \"\",\n \"compact\": \"coun\",\n \"jurisdiction\": \"md\",\n \"type\": \"privilege\",\n \"providerId\": \"4d5725cf-ad64-45fe-b60c-2224209edc4a\",\n \"status\": \"active\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"lifting_encumbrance\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"speech-language pathologist\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"mn\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"la\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"1994-03-26\",\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"1228-07-13\",\n \"privilegeId\": \"\",\n \"providerId\": \"38f48d47-72b6-4acd-9e61-da8cd93b2ad3\",\n \"dateOfRenewal\": \"2374-01-04\",\n \"dateOfUpdate\": \"\",\n \"status\": \"active\"\n }\n },\n {\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"ak\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"2781-12-27\",\n \"dateOfIssuance\": \"1187-05-12\",\n \"dateOfRenewal\": \"2763-09-21\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"de\",\n \"privilegeId\": \"\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"mo\",\n \"type\": \"privilege\",\n \"providerId\": \"a821d267-5653-49bd-97a2-bfc59170f24c\",\n \"status\": \"inactive\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"deactivation\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"occupational therapy assistant\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"in\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"id\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"1819-11-12\",\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"2038-12-26\",\n \"privilegeId\": \"\",\n \"providerId\": \"ad9f72d8-19d3-4b8e-b7b7-618dfe5e9e58\",\n \"dateOfRenewal\": \"1113-11-05\",\n \"dateOfUpdate\": \"\",\n \"status\": \"active\"\n }\n }\n ],\n \"jurisdiction\": \"wa\",\n \"licenseJurisdiction\": \"vt\",\n \"licenseType\": \"speech-language pathologist\",\n \"privilegeId\": \"\",\n \"providerId\": \"90bc19aa-772d-44ad-9cd6-3983304f2328\",\n \"status\": \"active\",\n \"type\": \"privilege\",\n \"investigationStatus\": \"underInvestigation\",\n \"investigations\": [\n {\n \"compact\": \"coun\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"mi\",\n \"licenseType\": \"\",\n \"providerId\": \"2dc8351a-6416-47e7-9b6e-2a34491c71c7\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"coun\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"ri\",\n \"licenseType\": \"\",\n \"providerId\": \"fe1216ae-9c2c-4b40-8c29-1d6b42028e71\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"octp\",\n \"creationDate\": \"1061-12-31\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1895-11-19\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"pr\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"2712681d-bded-4d93-905c-69072cc9ad3e\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2321-10-30\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"1298-05-03\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2743-07-01\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"or\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"c166bbe2-1f54-44d6-87ec-baf461c0b04e\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2562-12-05\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n }\n ]\n },\n {\n \"administratorSetStatus\": \"inactive\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compact\": \"coun\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1763-12-31\",\n \"dateOfIssuance\": \"1282-02-31\",\n \"dateOfRenewal\": \"1775-03-02\",\n \"dateOfUpdate\": \"\",\n \"history\": [\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"me\",\n \"previous\": {\n \"administratorSetStatus\": \"active\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1299-03-12\",\n \"dateOfIssuance\": \"2556-09-24\",\n \"dateOfRenewal\": \"2525-12-31\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"nc\",\n \"privilegeId\": \"\",\n \"compact\": \"aslp\",\n \"jurisdiction\": \"sc\",\n \"type\": \"privilege\",\n \"providerId\": \"fcee0893-045b-4e62-993b-2e1a0d5b9a6a\",\n \"status\": \"active\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"deactivation\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"licensed professional counselor\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"or\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"oh\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"2189-04-21\",\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"2561-09-02\",\n \"privilegeId\": \"\",\n \"providerId\": \"0e368060-5ad8-4526-a4d4-b3bb0ae375ee\",\n \"dateOfRenewal\": \"1197-12-28\",\n \"dateOfUpdate\": \"\",\n \"status\": \"active\"\n }\n },\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"nc\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"2675-12-14\",\n \"dateOfIssuance\": \"2405-08-11\",\n \"dateOfRenewal\": \"2288-09-14\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"az\",\n \"privilegeId\": \"\",\n \"compact\": \"coun\",\n \"jurisdiction\": \"hi\",\n \"type\": \"privilege\",\n \"providerId\": \"b442dc00-b160-49c2-9e00-adc648fe8e78\",\n \"status\": \"inactive\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"licenseDeactivation\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"occupational therapy assistant\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"md\",\n \"compact\": \"aslp\",\n \"jurisdiction\": \"ms\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"1632-10-06\",\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"2989-04-09\",\n \"privilegeId\": \"\",\n \"providerId\": \"f3981490-3938-4976-b957-7123142c46de\",\n \"dateOfRenewal\": \"1356-01-30\",\n \"dateOfUpdate\": \"\",\n \"status\": \"inactive\"\n }\n }\n ],\n \"jurisdiction\": \"ky\",\n \"licenseJurisdiction\": \"fl\",\n \"licenseType\": \"occupational therapist\",\n \"privilegeId\": \"\",\n \"providerId\": \"11ec1f36-39ef-411d-9ed6-0218a17bde45\",\n \"status\": \"inactive\",\n \"type\": \"privilege\",\n \"investigationStatus\": \"underInvestigation\",\n \"investigations\": [\n {\n \"compact\": \"aslp\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"va\",\n \"licenseType\": \"\",\n \"providerId\": \"f78a6f18-b3bd-4937-9a6c-b48d88245048\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"coun\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"oh\",\n \"licenseType\": \"\",\n \"providerId\": \"bf956261-d85b-4589-9cdb-446467415ef6\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"1572-08-30\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1663-10-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"nd\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"edea6027-5ae7-4c1f-a412-a29bed40d673\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1688-10-12\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"octp\",\n \"creationDate\": \"1780-08-11\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2812-12-13\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"al\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"f488d06a-d37c-4736-ba3a-c9d02cead4fa\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2996-12-11\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"providerId\": \"9b5458d5-9c52-41cd-b3b1-95f5c4affc2c\",\n \"type\": \"provider\",\n \"npi\": \"2535279913\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1940-02-02\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"currentHomeJurisdiction\": \"in\",\n \"ssnLastFour\": \"5671\",\n \"licenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"compactConnectRegisteredEmailAddress\": \"\"\n}", "code": 200, "cookie": [], "header": [ @@ -1300,7 +1300,7 @@ "value": "application/json" } ], - "id": "512a4239-02c5-46f6-a941-c12d567186bb", + "id": "b7cc6c87-9d25-473b-81fd-e94664497fc3", "name": "200 response", "originalRequest": { "body": {}, @@ -1358,7 +1358,7 @@ "item": [ { "event": [], - "id": "ddb6a23b-e741-469f-86c8-fffeccfe494d", + "id": "c8eaee6b-941a-4ba6-b5c2-86650cb9d0c7", "name": "/v1/compacts/:compact/providers/:providerId/licenses/jurisdiction/:jurisdiction/licenseType/:licenseType/encumbrance", "protocolProfileBehavior": { "disableBodyPruning": true @@ -1372,7 +1372,7 @@ "language": "json" } }, - "raw": "{\n \"encumbranceEffectiveDate\": \"1374-10-13\",\n \"encumbranceType\": \"modification of previous action-reduction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"clinicalPrivilegeActionCategory\": \"\"\n}" + "raw": "{\n \"encumbranceEffectiveDate\": \"1213-12-01\",\n \"encumbranceType\": \"modification of previous action-extension\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"clinicalPrivilegeActionCategory\": \"\"\n}" }, "description": {}, "header": [ @@ -1461,7 +1461,7 @@ "value": "application/json" } ], - "id": "0546c709-01f6-48af-8188-7bb7ce545fee", + "id": "1d4911ea-8ff0-4972-89ec-1328421603d7", "name": "200 response", "originalRequest": { "body": { @@ -1472,7 +1472,7 @@ "language": "json" } }, - "raw": "{\n \"encumbranceEffectiveDate\": \"1374-10-13\",\n \"encumbranceType\": \"modification of previous action-reduction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"clinicalPrivilegeActionCategory\": \"\"\n}" + "raw": "{\n \"encumbranceEffectiveDate\": \"1213-12-01\",\n \"encumbranceType\": \"modification of previous action-extension\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"clinicalPrivilegeActionCategory\": \"\"\n}" }, "header": [ { @@ -1523,7 +1523,7 @@ "item": [ { "event": [], - "id": "86336bac-1cc0-40ec-9aff-da7bf05456d6", + "id": "9cd0e9d6-8c28-4bd0-a03c-e498f58bd8d3", "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\": \"1227-11-31\"\n}" + "raw": "{\n \"effectiveLiftDate\": \"1104-03-15\"\n}" }, "description": {}, "header": [ @@ -1637,7 +1637,7 @@ "value": "application/json" } ], - "id": "1b56ea75-2ac2-47dd-9cce-536ad61b0715", + "id": "83eea677-31cb-483b-a142-c5017facf781", "name": "200 response", "originalRequest": { "body": { @@ -1648,7 +1648,7 @@ "language": "json" } }, - "raw": "{\n \"effectiveLiftDate\": \"1227-11-31\"\n}" + "raw": "{\n \"effectiveLiftDate\": \"1104-03-15\"\n}" }, "header": [ { @@ -1700,44 +1700,14 @@ } ], "name": "encumbrance" - } - ], - "name": "{licenseType}" - } - ], - "name": "licenseType" - } - ], - "name": "{jurisdiction}" - } - ], - "name": "jurisdiction" - } - ], - "name": "licenses" - }, - { - "description": "", - "item": [ - { - "description": "", - "item": [ - { - "description": "", - "item": [ - { - "description": "", - "item": [ - { - "description": "", - "item": [ + }, { "description": "", "item": [ { "event": [], - "id": "1a4164fb-221a-443a-b36a-4cb6c74d3206", - "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/deactivate", + "id": "95b5f3e9-5d82-4af1-9975-01b3fc520a7e", + "name": "/v1/compacts/:compact/providers/:providerId/licenses/jurisdiction/:jurisdiction/licenseType/:licenseType/investigation", "protocolProfileBehavior": { "disableBodyPruning": true }, @@ -1750,7 +1720,7 @@ "language": "json" } }, - "raw": "{\n \"deactivationNote\": \"\"\n}" + "raw": "{}" }, "description": {}, "header": [ @@ -1764,7 +1734,7 @@ } ], "method": "POST", - "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/deactivate", + "name": "/v1/compacts/:compact/providers/:providerId/licenses/jurisdiction/:jurisdiction/licenseType/:licenseType/investigation", "url": { "host": [ "{{baseUrl}}" @@ -1775,12 +1745,12 @@ ":compact", "providers", ":providerId", - "privileges", + "licenses", "jurisdiction", ":jurisdiction", "licenseType", ":licenseType", - "deactivate" + "investigation" ], "query": [], "variable": [ @@ -1839,7 +1809,7 @@ "value": "application/json" } ], - "id": "cc5c5986-e041-40ac-95da-c1a77ff06a1c", + "id": "c0b8a977-b11c-4ea6-9e30-cc2d9343e62a", "name": "200 response", "originalRequest": { "body": { @@ -1850,13 +1820,881 @@ "language": "json" } }, - "raw": "{\n \"deactivationNote\": \"\"\n}" + "raw": "{}" + }, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + }, + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "Authorization", + "value": "" + } + ], + "method": "POST", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "providers", + ":providerId", + "licenses", + "jurisdiction", + ":jurisdiction", + "licenseType", + ":licenseType", + "investigation" + ], + "query": [], + "variable": [] + } + }, + "status": "OK" + } + ] + }, + { + "description": "", + "item": [ + { + "event": [], + "id": "bde6e1f5-2b85-42d4-9af4-b096c2990722", + "name": "/v1/compacts/:compact/providers/:providerId/licenses/jurisdiction/:jurisdiction/licenseType/:licenseType/investigation/:investigationId", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "body": { + "mode": "raw", + "options": { + "raw": { + "headerFamily": "json", + "language": "json" + } + }, + "raw": "{\n \"action\": \"close\",\n \"encumbrance\": {\n \"encumbranceEffectiveDate\": \"2673-09-10\",\n \"encumbranceType\": \"public reprimand\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"clinicalPrivilegeActionCategory\": \"\"\n }\n}" }, + "description": {}, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "PATCH", + "name": "/v1/compacts/:compact/providers/:providerId/licenses/jurisdiction/:jurisdiction/licenseType/:licenseType/investigation/:investigationId", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "providers", + ":providerId", + "licenses", + "jurisdiction", + ":jurisdiction", + "licenseType", + ":licenseType", + "investigation", + ":investigationId" + ], + "query": [], + "variable": [ + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "compact", + "type": "any", + "value": "" + }, + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "providerId", + "type": "any", + "value": "" + }, + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "jurisdiction", + "type": "any", + "value": "" + }, + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "licenseType", + "type": "any", + "value": "" + }, + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "investigationId", + "type": "any", + "value": "" + } + ] + } + }, + "response": [ + { + "_postman_previewlanguage": "json", + "body": "{\n \"message\": \"\"\n}", + "code": 200, + "cookie": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "id": "6eb0f49a-cccf-469c-a8a6-8a4939443611", + "name": "200 response", + "originalRequest": { + "body": { + "mode": "raw", + "options": { + "raw": { + "headerFamily": "json", + "language": "json" + } + }, + "raw": "{\n \"action\": \"close\",\n \"encumbrance\": {\n \"encumbranceEffectiveDate\": \"2673-09-10\",\n \"encumbranceType\": \"public reprimand\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"clinicalPrivilegeActionCategory\": \"\"\n }\n}" + }, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + }, + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "Authorization", + "value": "" + } + ], + "method": "PATCH", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "providers", + ":providerId", + "licenses", + "jurisdiction", + ":jurisdiction", + "licenseType", + ":licenseType", + "investigation", + ":investigationId" + ], + "query": [], + "variable": [] + } + }, + "status": "OK" + } + ] + } + ], + "name": "{investigationId}" + } + ], + "name": "investigation" + } + ], + "name": "{licenseType}" + } + ], + "name": "licenseType" + } + ], + "name": "{jurisdiction}" + } + ], + "name": "jurisdiction" + } + ], + "name": "licenses" + }, + { + "description": "", + "item": [ + { + "description": "", + "item": [ + { + "description": "", + "item": [ + { + "description": "", + "item": [ + { + "description": "", + "item": [ + { + "description": "", + "item": [ + { + "event": [], + "id": "d74f25ec-cfee-4d03-b1f8-d3822c2be6dd", + "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/deactivate", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "body": { + "mode": "raw", + "options": { + "raw": { + "headerFamily": "json", + "language": "json" + } + }, + "raw": "{\n \"deactivationNote\": \"\"\n}" + }, + "description": {}, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "POST", + "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/deactivate", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "providers", + ":providerId", + "privileges", + "jurisdiction", + ":jurisdiction", + "licenseType", + ":licenseType", + "deactivate" + ], + "query": [], + "variable": [ + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "compact", + "type": "any", + "value": "" + }, + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "providerId", + "type": "any", + "value": "" + }, + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "jurisdiction", + "type": "any", + "value": "" + }, + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "licenseType", + "type": "any", + "value": "" + } + ] + } + }, + "response": [ + { + "_postman_previewlanguage": "json", + "body": "{\n \"message\": \"\"\n}", + "code": 200, + "cookie": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "id": "cff5ab64-b4a4-4921-8f90-2e694131358f", + "name": "200 response", + "originalRequest": { + "body": { + "mode": "raw", + "options": { + "raw": { + "headerFamily": "json", + "language": "json" + } + }, + "raw": "{\n \"deactivationNote\": \"\"\n}" + }, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + }, + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "Authorization", + "value": "" + } + ], + "method": "POST", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "providers", + ":providerId", + "privileges", + "jurisdiction", + ":jurisdiction", + "licenseType", + ":licenseType", + "deactivate" + ], + "query": [], + "variable": [] + } + }, + "status": "OK" + } + ] + } + ], + "name": "deactivate" + }, + { + "description": "", + "item": [ + { + "event": [], + "id": "37217a24-2f76-4243-8b84-2864dcff6606", + "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/encumbrance", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "body": { + "mode": "raw", + "options": { + "raw": { + "headerFamily": "json", + "language": "json" + } + }, + "raw": "{\n \"encumbranceEffectiveDate\": \"1213-12-01\",\n \"encumbranceType\": \"modification of previous action-extension\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"clinicalPrivilegeActionCategory\": \"\"\n}" + }, + "description": {}, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "POST", + "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/encumbrance", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "providers", + ":providerId", + "privileges", + "jurisdiction", + ":jurisdiction", + "licenseType", + ":licenseType", + "encumbrance" + ], + "query": [], + "variable": [ + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "compact", + "type": "any", + "value": "" + }, + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "providerId", + "type": "any", + "value": "" + }, + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "jurisdiction", + "type": "any", + "value": "" + }, + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "licenseType", + "type": "any", + "value": "" + } + ] + } + }, + "response": [ + { + "_postman_previewlanguage": "json", + "body": "{\n \"message\": \"\"\n}", + "code": 200, + "cookie": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "id": "2d0a8450-d518-4f25-adab-9d9c30782586", + "name": "200 response", + "originalRequest": { + "body": { + "mode": "raw", + "options": { + "raw": { + "headerFamily": "json", + "language": "json" + } + }, + "raw": "{\n \"encumbranceEffectiveDate\": \"1213-12-01\",\n \"encumbranceType\": \"modification of previous action-extension\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"clinicalPrivilegeActionCategory\": \"\"\n}" + }, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + }, + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "Authorization", + "value": "" + } + ], + "method": "POST", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "providers", + ":providerId", + "privileges", + "jurisdiction", + ":jurisdiction", + "licenseType", + ":licenseType", + "encumbrance" + ], + "query": [], + "variable": [] + } + }, + "status": "OK" + } + ] + }, + { + "description": "", + "item": [ + { + "event": [], + "id": "76f8edac-8274-4279-9db7-0ff6e00edb27", + "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/encumbrance/:encumbranceId", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "body": { + "mode": "raw", + "options": { + "raw": { + "headerFamily": "json", + "language": "json" + } + }, + "raw": "{\n \"effectiveLiftDate\": \"1104-03-15\"\n}" + }, + "description": {}, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "PATCH", + "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/encumbrance/:encumbranceId", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "providers", + ":providerId", + "privileges", + "jurisdiction", + ":jurisdiction", + "licenseType", + ":licenseType", + "encumbrance", + ":encumbranceId" + ], + "query": [], + "variable": [ + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "compact", + "type": "any", + "value": "" + }, + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "providerId", + "type": "any", + "value": "" + }, + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "jurisdiction", + "type": "any", + "value": "" + }, + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "licenseType", + "type": "any", + "value": "" + }, + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "encumbranceId", + "type": "any", + "value": "" + } + ] + } + }, + "response": [ + { + "_postman_previewlanguage": "json", + "body": "{\n \"message\": \"\"\n}", + "code": 200, + "cookie": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "id": "16981b5d-ab36-4364-9f2c-a0ec8435e010", + "name": "200 response", + "originalRequest": { + "body": { + "mode": "raw", + "options": { + "raw": { + "headerFamily": "json", + "language": "json" + } + }, + "raw": "{\n \"effectiveLiftDate\": \"1104-03-15\"\n}" + }, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + }, + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "Authorization", + "value": "" + } + ], + "method": "PATCH", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "providers", + ":providerId", + "privileges", + "jurisdiction", + ":jurisdiction", + "licenseType", + ":licenseType", + "encumbrance", + ":encumbranceId" + ], + "query": [], + "variable": [] + } + }, + "status": "OK" + } + ] + } + ], + "name": "{encumbranceId}" + } + ], + "name": "encumbrance" + }, + { + "description": "", + "item": [ + { + "event": [], + "id": "a3a9a8fc-e963-444d-ad98-28bbd589e0ff", + "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/history", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "body": {}, + "description": {}, + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/history", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "providers", + ":providerId", + "privileges", + "jurisdiction", + ":jurisdiction", + "licenseType", + ":licenseType", + "history" + ], + "query": [], + "variable": [ + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "compact", + "type": "any", + "value": "" + }, + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "providerId", + "type": "any", + "value": "" + }, + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "jurisdiction", + "type": "any", + "value": "" + }, + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "licenseType", + "type": "any", + "value": "" + } + ] + } + }, + "response": [ + { + "_postman_previewlanguage": "json", + "body": "{\n \"compact\": \"octp\",\n \"events\": [\n {\n \"createDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"effectiveDate\": \"1230-11-04\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"registration\",\n \"note\": \"\"\n },\n {\n \"createDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"effectiveDate\": \"2425-11-31\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"registration\",\n \"note\": \"\"\n }\n ],\n \"jurisdiction\": \"in\",\n \"licenseType\": \"speech-language pathologist\",\n \"privilegeId\": \"\",\n \"providerId\": \"d89a7efe-5041-4146-96a6-3f76cf1296f6\"\n}", + "code": 200, + "cookie": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "id": "f4fa57e2-59fc-4d08-9064-9c463bdf3623", + "name": "200 response", + "originalRequest": { + "body": {}, "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Accept", "value": "application/json" @@ -1870,7 +2708,7 @@ "value": "" } ], - "method": "POST", + "method": "GET", "url": { "host": [ "{{baseUrl}}" @@ -1886,7 +2724,7 @@ ":jurisdiction", "licenseType", ":licenseType", - "deactivate" + "history" ], "query": [], "variable": [] @@ -1897,15 +2735,15 @@ ] } ], - "name": "deactivate" + "name": "history" }, { "description": "", "item": [ { "event": [], - "id": "a652d362-1a74-4a53-a434-eccd030006f1", - "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/encumbrance", + "id": "de5fed5f-96c8-4f50-ad5c-db3229e0be7d", + "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/investigation", "protocolProfileBehavior": { "disableBodyPruning": true }, @@ -1918,7 +2756,7 @@ "language": "json" } }, - "raw": "{\n \"encumbranceEffectiveDate\": \"1374-10-13\",\n \"encumbranceType\": \"modification of previous action-reduction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"clinicalPrivilegeActionCategory\": \"\"\n}" + "raw": "{}" }, "description": {}, "header": [ @@ -1932,7 +2770,7 @@ } ], "method": "POST", - "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/encumbrance", + "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/investigation", "url": { "host": [ "{{baseUrl}}" @@ -1948,7 +2786,7 @@ ":jurisdiction", "licenseType", ":licenseType", - "encumbrance" + "investigation" ], "query": [], "variable": [ @@ -2007,7 +2845,7 @@ "value": "application/json" } ], - "id": "52e1d309-2101-4f9d-856d-1c22fe2f8e45", + "id": "48143152-8047-4218-9dc4-9ffe347769fc", "name": "200 response", "originalRequest": { "body": { @@ -2018,7 +2856,7 @@ "language": "json" } }, - "raw": "{\n \"encumbranceEffectiveDate\": \"1374-10-13\",\n \"encumbranceType\": \"modification of previous action-reduction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"clinicalPrivilegeActionCategory\": \"\"\n}" + "raw": "{}" }, "header": [ { @@ -2054,7 +2892,7 @@ ":jurisdiction", "licenseType", ":licenseType", - "encumbrance" + "investigation" ], "query": [], "variable": [] @@ -2069,8 +2907,8 @@ "item": [ { "event": [], - "id": "1d5f45e7-fb36-46fe-9280-c6fceb3c49ef", - "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/encumbrance/:encumbranceId", + "id": "ed605afe-0ca3-4ee9-9ec9-6f76a41f2824", + "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/investigation/:investigationId", "protocolProfileBehavior": { "disableBodyPruning": true }, @@ -2083,7 +2921,7 @@ "language": "json" } }, - "raw": "{\n \"effectiveLiftDate\": \"1227-11-31\"\n}" + "raw": "{\n \"action\": \"close\",\n \"encumbrance\": {\n \"encumbranceEffectiveDate\": \"2673-09-10\",\n \"encumbranceType\": \"public reprimand\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"clinicalPrivilegeActionCategory\": \"\"\n }\n}" }, "description": {}, "header": [ @@ -2097,7 +2935,7 @@ } ], "method": "PATCH", - "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/encumbrance/:encumbranceId", + "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/investigation/:investigationId", "url": { "host": [ "{{baseUrl}}" @@ -2113,8 +2951,8 @@ ":jurisdiction", "licenseType", ":licenseType", - "encumbrance", - ":encumbranceId" + "investigation", + ":investigationId" ], "query": [], "variable": [ @@ -2164,7 +3002,7 @@ "type": "text/plain" }, "disabled": false, - "key": "encumbranceId", + "key": "investigationId", "type": "any", "value": "" } @@ -2183,7 +3021,7 @@ "value": "application/json" } ], - "id": "1441370d-65bb-43d1-ae42-be7d0a48031b", + "id": "3d3b31c8-15e4-4652-9ced-ff23024a7cd0", "name": "200 response", "originalRequest": { "body": { @@ -2194,7 +3032,7 @@ "language": "json" } }, - "raw": "{\n \"effectiveLiftDate\": \"1227-11-31\"\n}" + "raw": "{\n \"action\": \"close\",\n \"encumbrance\": {\n \"encumbranceEffectiveDate\": \"2673-09-10\",\n \"encumbranceType\": \"public reprimand\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"clinicalPrivilegeActionCategory\": \"\"\n }\n}" }, "header": [ { @@ -2230,8 +3068,8 @@ ":jurisdiction", "licenseType", ":licenseType", - "encumbrance", - ":encumbranceId" + "investigation", + ":investigationId" ], "query": [], "variable": [] @@ -2242,152 +3080,10 @@ ] } ], - "name": "{encumbranceId}" - } - ], - "name": "encumbrance" - }, - { - "description": "", - "item": [ - { - "event": [], - "id": "a8cb4b3d-8c95-4096-928e-3f3ea7d894fc", - "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/history", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "body": {}, - "description": {}, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/history", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "compacts", - ":compact", - "providers", - ":providerId", - "privileges", - "jurisdiction", - ":jurisdiction", - "licenseType", - ":licenseType", - "history" - ], - "query": [], - "variable": [ - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "compact", - "type": "any", - "value": "" - }, - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "providerId", - "type": "any", - "value": "" - }, - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "jurisdiction", - "type": "any", - "value": "" - }, - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "licenseType", - "type": "any", - "value": "" - } - ] - } - }, - "response": [ - { - "_postman_previewlanguage": "json", - "body": "{\n \"compact\": \"octp\",\n \"events\": [\n {\n \"createDate\": \"2076-04-30\",\n \"dateOfUpdate\": \"2073-12-31\",\n \"effectiveDate\": \"2942-07-31\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"deactivation\",\n \"note\": \"\"\n },\n {\n \"createDate\": \"1430-12-20\",\n \"dateOfUpdate\": \"2556-06-30\",\n \"effectiveDate\": \"2731-07-30\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"registration\",\n \"note\": \"\"\n }\n ],\n \"jurisdiction\": \"or\",\n \"licenseType\": \"occupational therapist\",\n \"privilegeId\": \"\",\n \"providerId\": \"629b1a4f-7c8c-47cf-a9be-aabf6bf4e69e\"\n}", - "code": 200, - "cookie": [], - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "id": "48e29d51-12b0-4477-953b-d0fe92b8dd78", - "name": "200 response", - "originalRequest": { - "body": {}, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "compacts", - ":compact", - "providers", - ":providerId", - "privileges", - "jurisdiction", - ":jurisdiction", - "licenseType", - ":licenseType", - "history" - ], - "query": [], - "variable": [] - } - }, - "status": "OK" - } - ] + "name": "{investigationId}" } ], - "name": "history" + "name": "investigation" } ], "name": "{licenseType}" @@ -2409,7 +3105,7 @@ "item": [ { "event": [], - "id": "ab2d0ef3-5216-4d3e-b32c-9b0d1dcaff52", + "id": "d369e9fc-9a44-4e34-9f10-f4b7d700268b", "name": "/v1/compacts/:compact/providers/:providerId/ssn", "protocolProfileBehavior": { "disableBodyPruning": true @@ -2465,7 +3161,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"ssn\": \"800-97-5405\"\n}", + "body": "{\n \"ssn\": \"227-14-4166\"\n}", "code": 200, "cookie": [], "header": [ @@ -2474,7 +3170,7 @@ "value": "application/json" } ], - "id": "7c33d4d3-7398-4140-ac69-a1c5c872f4ee", + "id": "e4d02317-6458-4453-901b-b355ecea038e", "name": "200 response", "originalRequest": { "body": {}, @@ -2527,7 +3223,7 @@ "item": [ { "event": [], - "id": "d0013ea7-8b5a-45e2-b88b-2168845c48f9", + "id": "c631e2a7-bf4c-49da-8ebe-44c63db91758", "name": "/v1/compacts/:compact/staff-users", "protocolProfileBehavior": { "disableBodyPruning": true @@ -2571,7 +3267,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 \"dolor_f\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"deserunt_f\": {\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 \"officiaa07\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"ut_841\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"reprehenderit__83\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"magna_e2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"enim8\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"dolore_17\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"irure6\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"pariatur_d1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"dolored0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"inactive\",\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 \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"inactive\",\n \"userId\": \"\"\n },\n {\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"active\",\n \"userId\": \"\"\n }\n ]\n}", "code": 200, "cookie": [], "header": [ @@ -2589,7 +3285,7 @@ "value": "" } ], - "id": "3fcc47bf-17c8-42af-9bb7-0efeae4cf2d9", + "id": "f82015f7-677a-410b-b0fa-a0b2a610cd86", "name": "200 response", "originalRequest": { "body": {}, @@ -2628,7 +3324,7 @@ }, { "event": [], - "id": "6510ac56-b3f6-4012-b24c-a9e233011dbf", + "id": "29d97a42-4046-4876-8a17-84064d3ddd00", "name": "/v1/compacts/:compact/staff-users", "protocolProfileBehavior": { "disableBodyPruning": true @@ -2642,7 +3338,7 @@ "language": "json" } }, - "raw": "{\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"incididuntb7\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"ea_d\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"in4f6\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"reprehenderit_a2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"occaecat3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"nisi_b\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"ut_3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n }\n}" + "raw": "{\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n }\n}" }, "description": {}, "header": [ @@ -2685,7 +3381,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"officia_25\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"exercitation_2d6\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"proident8e6\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"Duis_912\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"non9\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"fugiat_\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"inactive\",\n \"userId\": \"\"\n}", + "body": "{\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"active\",\n \"userId\": \"\"\n}", "code": 200, "cookie": [], "header": [ @@ -2703,7 +3399,7 @@ "value": "" } ], - "id": "7be83ef7-5255-4e95-bf52-65dc18487b97", + "id": "f6195ee8-b64e-4893-a4a6-6d3a6dc4a12f", "name": "200 response", "originalRequest": { "body": { @@ -2714,7 +3410,7 @@ "language": "json" } }, - "raw": "{\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"incididuntb7\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"ea_d\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"in4f6\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"reprehenderit_a2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"occaecat3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"nisi_b\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"ut_3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n }\n}" + "raw": "{\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n }\n}" }, "header": [ { @@ -2758,7 +3454,7 @@ "item": [ { "event": [], - "id": "6c788cc6-0f48-42fe-a8fb-b0996c4e66b3", + "id": "b2755dc9-d52a-4cff-a777-90f4b620047d", "name": "/v1/compacts/:compact/staff-users/:userId", "protocolProfileBehavior": { "disableBodyPruning": true @@ -2813,7 +3509,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"officia_25\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"exercitation_2d6\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"proident8e6\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"Duis_912\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"non9\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"fugiat_\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"inactive\",\n \"userId\": \"\"\n}", + "body": "{\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"active\",\n \"userId\": \"\"\n}", "code": 200, "cookie": [], "header": [ @@ -2831,7 +3527,7 @@ "value": "" } ], - "id": "b9a6a299-3522-4276-9ec2-62b6cd9061a1", + "id": "ff17dff0-146d-45cb-a8da-d8ae414cb769", "name": "200 response", "originalRequest": { "body": {}, @@ -2878,7 +3574,7 @@ "value": "application/json" } ], - "id": "4adec6a5-5ac8-46ec-9cea-5f7f69f63552", + "id": "33b13941-da68-4139-b292-78d80a65e7dc", "name": "404 response", "originalRequest": { "body": {}, @@ -2918,7 +3614,7 @@ }, { "event": [], - "id": "faac95c0-dda5-4354-b7f6-7c8224d80055", + "id": "1b4f0101-5b64-45c0-80c4-a1cd49535c02", "name": "/v1/compacts/:compact/staff-users/:userId", "protocolProfileBehavior": { "disableBodyPruning": true @@ -2982,7 +3678,7 @@ "value": "application/json" } ], - "id": "f37e0722-008d-481d-a42c-a3f79f8214d5", + "id": "ecd839cc-ba61-4083-95bd-aa680c301748", "name": "200 response", "originalRequest": { "body": {}, @@ -3029,7 +3725,7 @@ "value": "application/json" } ], - "id": "ed1880c9-d3c6-48ec-8483-51c82e1d293d", + "id": "65646a0b-ca6d-4a2d-8126-cd2f25299dc8", "name": "404 response", "originalRequest": { "body": {}, @@ -3069,7 +3765,7 @@ }, { "event": [], - "id": "a47cb45f-1f16-453d-8878-eb05ed833ee8", + "id": "cbf95d6f-f5b2-4583-8610-e41700a748d6", "name": "/v1/compacts/:compact/staff-users/:userId", "protocolProfileBehavior": { "disableBodyPruning": true @@ -3083,7 +3779,7 @@ "language": "json" } }, - "raw": "{\n \"permissions\": {\n \"nonc4\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"dolor88c\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"adipisicing_63\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"laboris98a\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"mollita1a\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"non4b9\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"dolor3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"do__\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"occaecat_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"fugiat5\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n }\n}" + "raw": "{\n \"permissions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n }\n}" }, "description": {}, "header": [ @@ -3137,7 +3833,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"officia_25\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"exercitation_2d6\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"proident8e6\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"Duis_912\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"non9\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"fugiat_\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"inactive\",\n \"userId\": \"\"\n}", + "body": "{\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"active\",\n \"userId\": \"\"\n}", "code": 200, "cookie": [], "header": [ @@ -3155,7 +3851,7 @@ "value": "" } ], - "id": "6cd6fa0f-c3f1-4eb6-870d-962a75127d25", + "id": "b9442f93-4e9e-43c8-aa18-4306a45625ea", "name": "200 response", "originalRequest": { "body": { @@ -3166,7 +3862,7 @@ "language": "json" } }, - "raw": "{\n \"permissions\": {\n \"nonc4\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"dolor88c\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"adipisicing_63\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"laboris98a\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"mollita1a\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"non4b9\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"dolor3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"do__\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"occaecat_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"fugiat5\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n }\n}" + "raw": "{\n \"permissions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n }\n}" }, "header": [ { @@ -3215,7 +3911,7 @@ "value": "application/json" } ], - "id": "e18b71ff-9c60-4064-bb86-483b0159a83c", + "id": "858ab0b2-7cba-4745-b11d-d0e2a111cae5", "name": "404 response", "originalRequest": { "body": { @@ -3226,7 +3922,7 @@ "language": "json" } }, - "raw": "{\n \"permissions\": {\n \"nonc4\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"dolor88c\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"adipisicing_63\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"laboris98a\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"mollita1a\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"non4b9\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"dolor3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"do__\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"occaecat_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"fugiat5\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n }\n}" + "raw": "{\n \"permissions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n }\n}" }, "header": [ { @@ -3271,7 +3967,7 @@ "item": [ { "event": [], - "id": "f08dcc99-66b0-4c52-981c-85861c222ca4", + "id": "997abfd5-71da-47e6-acc9-f610c951a189", "name": "/v1/compacts/:compact/staff-users/:userId/reinvite", "protocolProfileBehavior": { "disableBodyPruning": true @@ -3336,7 +4032,7 @@ "value": "application/json" } ], - "id": "ecf11a79-81a1-4f45-9be1-27c4f92bd712", + "id": "aa1266af-7d68-42d7-bec9-d0eb27d1e8e9", "name": "200 response", "originalRequest": { "body": {}, @@ -3384,7 +4080,7 @@ "value": "application/json" } ], - "id": "a216484d-3c39-4539-8c73-fe2264f8fe76", + "id": "ea46b05f-ab59-4d7a-bfe5-439fc806a761", "name": "404 response", "originalRequest": { "body": {}, @@ -3449,7 +4145,7 @@ "item": [ { "event": [], - "id": "f998ed90-65ef-4574-982d-e74f490a03c6", + "id": "4297e674-e814-426c-abf4-ca1c66004bde", "name": "/v1/flags/:flagId/check", "protocolProfileBehavior": { "disableBodyPruning": true @@ -3466,7 +4162,7 @@ "language": "json" } }, - "raw": "{\n \"context\": {\n \"userId\": \"\",\n \"customAttributes\": {\n \"cillum__f\": \"\",\n \"in_dab\": \"\",\n \"eiusmod63\": \"\"\n }\n }\n}" + "raw": "{\n \"context\": {\n \"userId\": \"\",\n \"customAttributes\": {\n \"key_0\": \"\",\n \"key_1\": \"\"\n }\n }\n}" }, "description": {}, "header": [ @@ -3518,7 +4214,7 @@ "value": "application/json" } ], - "id": "63181a7e-3f53-4525-9165-6e44e9203dba", + "id": "f6caa750-5414-4442-a2fa-0ef078eef64a", "name": "200 response", "originalRequest": { "body": { @@ -3529,7 +4225,7 @@ "language": "json" } }, - "raw": "{\n \"context\": {\n \"userId\": \"\",\n \"customAttributes\": {\n \"cillum__f\": \"\",\n \"in_dab\": \"\",\n \"eiusmod63\": \"\"\n }\n }\n}" + "raw": "{\n \"context\": {\n \"userId\": \"\",\n \"customAttributes\": {\n \"key_0\": \"\",\n \"key_1\": \"\"\n }\n }\n}" }, "header": [ { @@ -3587,7 +4283,7 @@ "item": [ { "event": [], - "id": "75981dbd-08de-4f58-85b4-49ff1b4c4ab2", + "id": "e21d0d72-bd18-47cc-8b5b-33b4352dc8b7", "name": "/v1/provider-users/initiateRecovery", "protocolProfileBehavior": { "disableBodyPruning": true @@ -3604,7 +4300,7 @@ "language": "json" } }, - "raw": "{\n \"compact\": \"octp\",\n \"dob\": \"1878-12-30\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdiction\": \"ak\",\n \"licenseType\": \"licensed professional counselor\",\n \"partialSocial\": \"0818\",\n \"password\": \"\",\n \"recaptchaToken\": \"\",\n \"username\": \"\"\n}" + "raw": "{\n \"compact\": \"octp\",\n \"dob\": \"1928-01-30\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdiction\": \"vt\",\n \"licenseType\": \"occupational therapy assistant\",\n \"partialSocial\": \"4620\",\n \"password\": \"\",\n \"recaptchaToken\": \"\",\n \"username\": \"\"\n}" }, "description": {}, "header": [ @@ -3644,7 +4340,7 @@ "value": "application/json" } ], - "id": "3b850382-90d7-4229-8439-da59d5446ad3", + "id": "aab9dc02-e8ff-4f4a-8dcf-8c53a2d3588d", "name": "200 response", "originalRequest": { "body": { @@ -3655,7 +4351,7 @@ "language": "json" } }, - "raw": "{\n \"compact\": \"octp\",\n \"dob\": \"1878-12-30\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdiction\": \"ak\",\n \"licenseType\": \"licensed professional counselor\",\n \"partialSocial\": \"0818\",\n \"password\": \"\",\n \"recaptchaToken\": \"\",\n \"username\": \"\"\n}" + "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}" }, "header": [ { @@ -3693,7 +4389,7 @@ "item": [ { "event": [], - "id": "35b736e9-e074-48ed-a195-3972a61c841c", + "id": "a764b1b7-b990-4ab1-8ed1-dd8e23abb02c", "name": "/v1/provider-users/me", "protocolProfileBehavior": { "disableBodyPruning": true @@ -3725,7 +4421,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"birthMonthDay\": \"10-13\",\n \"compact\": \"octp\",\n \"dateOfExpiration\": \"2708-12-19\",\n \"dateOfUpdate\": \"2235-11-07\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"licenseJurisdiction\": \"or\",\n \"licenses\": [\n {\n \"compact\": \"octp\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"1194-11-07\",\n \"dateOfIssuance\": \"1811-01-06\",\n \"dateOfRenewal\": \"1005-01-30\",\n \"dateOfUpdate\": \"1228-10-17\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"history\": [\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"1122-08-31\",\n \"jurisdiction\": \"de\",\n \"previous\": {\n \"dateOfExpiration\": \"2825-06-30\",\n \"dateOfIssuance\": \"2552-11-09\",\n \"dateOfRenewal\": \"1554-03-08\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"7904266478\",\n \"compactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1130-10-09\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+127199520822501\",\n \"licenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"expiration\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"audiologist\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"npi\": \"7996207438\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"2689-12-23\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2349-01-07\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"1981-10-31\",\n \"phoneNumber\": \"+48186155665\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"2985-03-02\",\n \"licenseStatus\": \"active\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n },\n {\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"2799-03-17\",\n \"jurisdiction\": \"pa\",\n \"previous\": {\n \"dateOfExpiration\": \"1712-01-28\",\n \"dateOfIssuance\": \"2124-05-31\",\n \"dateOfRenewal\": \"1804-12-25\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"3975501387\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"1678-08-23\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+80432649\",\n \"licenseStatus\": \"inactive\",\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\": \"5745410699\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"2459-01-20\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2074-05-31\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"1606-04-30\",\n \"phoneNumber\": \"+38094519029451\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"2402-11-03\",\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\": \"mo\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"audiologist\",\n \"middleName\": \"\",\n \"providerId\": \"d6a677d3-d7fb-4780-9aa5-0b54def3f5fb\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"licenseNumber\": \"\",\n \"npi\": \"6162231934\",\n \"dateOfBirth\": \"2116-11-23\",\n \"ssnLastFour\": \"2827\",\n \"phoneNumber\": \"+316315679209\",\n \"licenseStatusName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"2958-09-30\",\n \"dateOfUpdate\": \"1010-09-30\",\n \"effectiveStartDate\": \"2967-11-11\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"md\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"92cf3f74-4eba-4e03-8960-fe8244789ade\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2333-10-23\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"2829-11-23\",\n \"dateOfUpdate\": \"1986-11-31\",\n \"effectiveStartDate\": \"1863-08-02\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"ca\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"3ebb12e1-61e9-4764-bc27-deb0c708968b\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2426-05-14\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n }\n ]\n },\n {\n \"compact\": \"coun\",\n \"compactEligibility\": \"eligible\",\n \"dateOfExpiration\": \"2303-05-13\",\n \"dateOfIssuance\": \"1600-02-08\",\n \"dateOfRenewal\": \"1136-11-23\",\n \"dateOfUpdate\": \"2758-11-26\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"history\": [\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"2228-10-08\",\n \"jurisdiction\": \"or\",\n \"previous\": {\n \"dateOfExpiration\": \"1909-03-15\",\n \"dateOfIssuance\": \"1920-12-19\",\n \"dateOfRenewal\": \"1820-11-05\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"4309547090\",\n \"compactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2575-01-06\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+282921155\",\n \"licenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"encumbrance\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"audiologist\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"npi\": \"1272654411\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"2191-12-26\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2166-09-22\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"2233-10-31\",\n \"phoneNumber\": \"+83080416937936\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"1714-09-03\",\n \"licenseStatus\": \"inactive\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n },\n {\n \"compact\": \"aslp\",\n \"dateOfUpdate\": \"1525-12-30\",\n \"jurisdiction\": \"ca\",\n \"previous\": {\n \"dateOfExpiration\": \"1149-10-31\",\n \"dateOfIssuance\": \"1206-01-04\",\n \"dateOfRenewal\": \"2125-10-28\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"7139133672\",\n \"compactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2503-11-06\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+22157243484\",\n \"licenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"issuance\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"occupational therapist\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"npi\": \"5902669871\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"1846-06-16\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"1530-10-31\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"2630-10-29\",\n \"phoneNumber\": \"+463314989084\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"2898-04-19\",\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\": \"ri\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"occupational therapy assistant\",\n \"middleName\": \"\",\n \"providerId\": \"90bf4c45-b2bc-4e1c-9055-23adc019b9ff\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"licenseNumber\": \"\",\n \"npi\": \"6796883381\",\n \"dateOfBirth\": \"2836-10-24\",\n \"ssnLastFour\": \"3288\",\n \"phoneNumber\": \"+58540754234\",\n \"licenseStatusName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"aslp\",\n \"creationDate\": \"1925-08-08\",\n \"dateOfUpdate\": \"2975-04-19\",\n \"effectiveStartDate\": \"2776-11-04\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"tn\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"17990d99-1def-4abd-8a8a-3122ca3219da\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2663-04-30\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"octp\",\n \"creationDate\": \"1285-04-03\",\n \"dateOfUpdate\": \"2856-10-30\",\n \"effectiveStartDate\": \"2544-08-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"ky\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"2b1d1723-dfb8-40bc-90cf-902c703548c9\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1523-10-10\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"militaryAffiliations\": [\n {\n \"affiliationType\": \"militaryMemberSpouse\",\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"2565-05-30\",\n \"dateOfUpload\": \"2261-11-03\",\n \"fileNames\": [\n \"\",\n \"\"\n ],\n \"providerId\": \"84e50f08-03c5-4137-b371-2550b5369cdb\",\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\": \"2471-04-28\",\n \"dateOfUpload\": \"2015-12-30\",\n \"fileNames\": [\n \"\",\n \"\"\n ],\n \"providerId\": \"6850e115-8f7f-4f2e-bb91-d0710a6fc6c7\",\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 \"il\",\n \"wa\"\n ],\n \"privileges\": [\n {\n \"administratorSetStatus\": \"inactive\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compact\": \"aslp\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1403-11-30\",\n \"dateOfIssuance\": \"2058-06-04\",\n \"dateOfRenewal\": \"2848-04-31\",\n \"dateOfUpdate\": \"2721-10-29\",\n \"history\": [\n {\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"2232-10-08\",\n \"jurisdiction\": \"hi\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"2380-07-31\",\n \"dateOfIssuance\": \"1675-11-01\",\n \"dateOfRenewal\": \"1533-01-06\",\n \"dateOfUpdate\": \"1664-10-31\",\n \"licenseJurisdiction\": \"dc\",\n \"privilegeId\": \"\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"oh\",\n \"type\": \"privilege\",\n \"providerId\": \"f215ffd6-257c-40c5-8daa-9a21135d1b03\",\n \"status\": \"inactive\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"lifting_encumbrance\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"occupational therapist\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"id\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"sc\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"2178-07-01\",\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"2128-01-07\",\n \"privilegeId\": \"\",\n \"providerId\": \"11b11933-f36a-4eb7-83a0-542011f49873\",\n \"dateOfRenewal\": \"2696-05-20\",\n \"dateOfUpdate\": \"1259-03-06\",\n \"status\": \"inactive\"\n }\n },\n {\n \"compact\": \"aslp\",\n \"dateOfUpdate\": \"1899-11-28\",\n \"jurisdiction\": \"id\",\n \"previous\": {\n \"administratorSetStatus\": \"active\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1808-12-25\",\n \"dateOfIssuance\": \"1867-04-31\",\n \"dateOfRenewal\": \"1948-03-09\",\n \"dateOfUpdate\": \"1544-11-09\",\n \"licenseJurisdiction\": \"pr\",\n \"privilegeId\": \"\",\n \"compact\": \"coun\",\n \"jurisdiction\": \"ak\",\n \"type\": \"privilege\",\n \"providerId\": \"98e3f59c-0fbc-4aa4-bce1-7d5689f18812\",\n \"status\": \"inactive\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"renewal\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"occupational therapist\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"wv\",\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\": \"1221-02-30\",\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"2814-11-15\",\n \"privilegeId\": \"\",\n \"providerId\": \"532d563d-a1bc-40dc-a5f0-8a0a86f05451\",\n \"dateOfRenewal\": \"1885-12-31\",\n \"dateOfUpdate\": \"1384-02-11\",\n \"status\": \"active\"\n }\n }\n ],\n \"jurisdiction\": \"vt\",\n \"licenseJurisdiction\": \"mn\",\n \"licenseType\": \"occupational therapy assistant\",\n \"privilegeId\": \"\",\n \"providerId\": \"4b524f8c-6a30-4ee6-ab6c-55c37a68167f\",\n \"status\": \"inactive\",\n \"type\": \"privilege\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"octp\",\n \"creationDate\": \"2986-01-02\",\n \"dateOfUpdate\": \"1994-10-13\",\n \"effectiveStartDate\": \"1242-11-28\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"sc\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"bbf662ac-7e91-4140-903c-0c350d7dc5f7\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2961-12-04\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"octp\",\n \"creationDate\": \"2886-12-31\",\n \"dateOfUpdate\": \"2098-11-30\",\n \"effectiveStartDate\": \"1188-03-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"me\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"7a3ab2b7-4041-47eb-94c4-e50ec792a958\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2457-03-28\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n }\n ]\n },\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\": \"1412-03-31\",\n \"dateOfIssuance\": \"1750-01-08\",\n \"dateOfRenewal\": \"2654-02-08\",\n \"dateOfUpdate\": \"1882-06-30\",\n \"history\": [\n {\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"1696-11-03\",\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\": \"2747-09-30\",\n \"dateOfIssuance\": \"2819-02-21\",\n \"dateOfRenewal\": \"1924-12-14\",\n \"dateOfUpdate\": \"2059-01-31\",\n \"licenseJurisdiction\": \"pr\",\n \"privilegeId\": \"\",\n \"compact\": \"coun\",\n \"jurisdiction\": \"ks\",\n \"type\": \"privilege\",\n \"providerId\": \"81e91006-0dfe-4322-8a41-b7d78aae5f69\",\n \"status\": \"active\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"licenseDeactivation\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"speech-language pathologist\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"az\",\n \"compact\": \"coun\",\n \"jurisdiction\": \"sc\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"1608-08-04\",\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"2931-11-31\",\n \"privilegeId\": \"\",\n \"providerId\": \"c4cc5de7-00ec-43e9-97f0-5dab5c0ae1a6\",\n \"dateOfRenewal\": \"1003-10-19\",\n \"dateOfUpdate\": \"1809-12-04\",\n \"status\": \"inactive\"\n }\n },\n {\n \"compact\": \"aslp\",\n \"dateOfUpdate\": \"1973-11-16\",\n \"jurisdiction\": \"ia\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1210-10-31\",\n \"dateOfIssuance\": \"1470-12-02\",\n \"dateOfRenewal\": \"2582-01-31\",\n \"dateOfUpdate\": \"1348-05-14\",\n \"licenseJurisdiction\": \"ks\",\n \"privilegeId\": \"\",\n \"compact\": \"coun\",\n \"jurisdiction\": \"ok\",\n \"type\": \"privilege\",\n \"providerId\": \"e364f6bd-3d30-4881-ad2e-99d25dd13612\",\n \"status\": \"inactive\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"emailChange\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"speech-language pathologist\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"md\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"me\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"1321-02-30\",\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"1415-12-13\",\n \"privilegeId\": \"\",\n \"providerId\": \"2550a822-a6a2-46f5-9d6f-381ad5c7ad50\",\n \"dateOfRenewal\": \"1441-10-20\",\n \"dateOfUpdate\": \"2823-01-02\",\n \"status\": \"active\"\n }\n }\n ],\n \"jurisdiction\": \"ky\",\n \"licenseJurisdiction\": \"ne\",\n \"licenseType\": \"speech-language pathologist\",\n \"privilegeId\": \"\",\n \"providerId\": \"e7d03cad-55f3-4a69-bf06-109772196128\",\n \"status\": \"inactive\",\n \"type\": \"privilege\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"1836-11-03\",\n \"dateOfUpdate\": \"1386-07-12\",\n \"effectiveStartDate\": \"2383-04-14\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"ar\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"91d01ece-89f1-46d0-8105-52a9517f6a70\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1297-12-28\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"1188-10-30\",\n \"dateOfUpdate\": \"2977-10-31\",\n \"effectiveStartDate\": \"2846-08-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"fl\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"5205a6c0-1147-4655-a17b-ff3780c163d9\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1879-11-24\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"providerId\": \"94022a97-0d0d-4153-bda8-a15afc713ee2\",\n \"type\": \"provider\",\n \"npi\": \"8197107476\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2021-12-30\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"suffix\": \"\",\n \"currentHomeJurisdiction\": \"sc\",\n \"ssnLastFour\": \"2592\",\n \"licenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"compactConnectRegisteredEmailAddress\": \"\"\n}", + "body": "{\n \"birthMonthDay\": \"08-15\",\n \"compact\": \"octp\",\n \"dateOfExpiration\": \"2385-12-29\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"licenseJurisdiction\": \"nc\",\n \"licenses\": [\n {\n \"compact\": \"octp\",\n \"compactEligibility\": \"eligible\",\n \"dateOfExpiration\": \"2936-05-05\",\n \"dateOfIssuance\": \"2254-10-17\",\n \"dateOfRenewal\": \"2835-10-30\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"history\": [\n {\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"md\",\n \"previous\": {\n \"dateOfExpiration\": \"1800-04-06\",\n \"dateOfIssuance\": \"2972-08-07\",\n \"dateOfRenewal\": \"2438-09-14\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"6466950304\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"2691-05-23\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+855527173535\",\n \"licenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"deactivation\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"occupational therapist\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"npi\": \"8394405807\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"eligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1842-07-31\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2300-08-05\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"2863-08-03\",\n \"phoneNumber\": \"+3528187300\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"2856-05-08\",\n \"licenseStatus\": \"inactive\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n },\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"mt\",\n \"previous\": {\n \"dateOfExpiration\": \"1918-10-05\",\n \"dateOfIssuance\": \"1476-12-15\",\n \"dateOfRenewal\": \"1145-11-31\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"0596273100\",\n \"compactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1028-12-30\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+9136425909\",\n \"licenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"lifting_encumbrance\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"speech-language pathologist\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"npi\": \"2808925314\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"1326-05-06\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2399-06-25\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"2473-11-30\",\n \"phoneNumber\": \"+1013464869\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"2257-10-20\",\n \"licenseStatus\": \"active\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n }\n ],\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdiction\": \"hi\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"licenseStatus\": \"inactive\",\n \"licenseType\": \"occupational therapy assistant\",\n \"middleName\": \"\",\n \"providerId\": \"9b0bf21b-92f8-41d9-9df4-eaabd84adeee\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"investigations\": [\n {\n \"compact\": \"coun\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"ut\",\n \"licenseType\": \"\",\n \"providerId\": \"ad1db718-9d5c-4842-8b45-9b34ee4e0cca\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"octp\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"vt\",\n \"licenseType\": \"\",\n \"providerId\": \"844db6f5-44af-4fc9-9221-3015ed96cbef\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"licenseNumber\": \"\",\n \"investigationStatus\": \"underInvestigation\",\n \"npi\": \"2619765297\",\n \"dateOfBirth\": \"1768-09-08\",\n \"ssnLastFour\": \"4600\",\n \"phoneNumber\": \"+59112126684\",\n \"licenseStatusName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"octp\",\n \"creationDate\": \"1777-09-23\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2708-03-02\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"ms\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"aeca0267-7733-48d0-a71c-d884e4754f14\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2396-01-09\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"aslp\",\n \"creationDate\": \"2958-05-22\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2261-06-31\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"mt\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"ba21be91-43db-4c7a-98de-607d32eb34b6\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1185-12-14\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n }\n ]\n },\n {\n \"compact\": \"aslp\",\n \"compactEligibility\": \"eligible\",\n \"dateOfExpiration\": \"1169-12-27\",\n \"dateOfIssuance\": \"2302-07-11\",\n \"dateOfRenewal\": \"1624-03-08\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"history\": [\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"hi\",\n \"previous\": {\n \"dateOfExpiration\": \"2804-01-30\",\n \"dateOfIssuance\": \"1778-03-30\",\n \"dateOfRenewal\": \"1452-12-01\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"8839374275\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"2408-12-29\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+25666997428056\",\n \"licenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"homeJurisdictionChange\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"speech-language pathologist\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"npi\": \"3527141483\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"eligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1539-11-04\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2033-10-28\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"2946-02-27\",\n \"phoneNumber\": \"+11739899598\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"1036-10-30\",\n \"licenseStatus\": \"active\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n },\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"dc\",\n \"previous\": {\n \"dateOfExpiration\": \"2132-01-04\",\n \"dateOfIssuance\": \"2948-11-30\",\n \"dateOfRenewal\": \"1580-10-30\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"3443777389\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"2208-08-30\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+69171972\",\n \"licenseStatus\": \"active\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"deactivation\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"speech-language pathologist\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"npi\": \"2199245845\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"eligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2289-12-31\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2595-12-09\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"2635-11-09\",\n \"phoneNumber\": \"+538742548588950\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"1656-08-16\",\n \"licenseStatus\": \"active\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n }\n ],\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdiction\": \"ky\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"occupational therapist\",\n \"middleName\": \"\",\n \"providerId\": \"481ba68e-ab28-427f-befb-fba6bf3b3bae\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"investigations\": [\n {\n \"compact\": \"aslp\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"wa\",\n \"licenseType\": \"\",\n \"providerId\": \"f1c029aa-dae1-4ce3-a43c-1bf2320b7642\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"aslp\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"wv\",\n \"licenseType\": \"\",\n \"providerId\": \"8c92261b-fea9-470a-8fe8-dcaca7ca8697\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"licenseNumber\": \"\",\n \"investigationStatus\": \"underInvestigation\",\n \"npi\": \"1690493112\",\n \"dateOfBirth\": \"1751-01-02\",\n \"ssnLastFour\": \"8079\",\n \"phoneNumber\": \"+106891529\",\n \"licenseStatusName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"1394-05-20\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1355-11-25\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"mn\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"6146bf62-a9f2-4c96-9d67-8c32cf05de02\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1150-11-05\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"2103-02-30\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2104-10-21\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"al\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"bd5c9993-b19c-49a9-a241-6b67768bb1a5\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1567-09-31\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"militaryAffiliations\": [\n {\n \"affiliationType\": \"militaryMember\",\n \"compact\": \"aslp\",\n \"dateOfUpdate\": \"\",\n \"dateOfUpload\": \"2814-11-05\",\n \"fileNames\": [\n \"\",\n \"\"\n ],\n \"providerId\": \"a6dad0d8-c0c7-4274-9d1f-a530a54209d2\",\n \"status\": \"active\",\n \"type\": \"militaryAffiliation\",\n \"downloadLinks\": [\n {\n \"fileName\": \"\",\n \"url\": \"\"\n },\n {\n \"fileName\": \"\",\n \"url\": \"\"\n }\n ]\n },\n {\n \"affiliationType\": \"militaryMemberSpouse\",\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"dateOfUpload\": \"2214-04-11\",\n \"fileNames\": [\n \"\",\n \"\"\n ],\n \"providerId\": \"ca2d993d-8064-4a9a-9db0-f22f39e37118\",\n \"status\": \"active\",\n \"type\": \"militaryAffiliation\",\n \"downloadLinks\": [\n {\n \"fileName\": \"\",\n \"url\": \"\"\n },\n {\n \"fileName\": \"\",\n \"url\": \"\"\n }\n ]\n }\n ],\n \"privilegeJurisdictions\": [\n \"az\",\n \"ga\"\n ],\n \"privileges\": [\n {\n \"administratorSetStatus\": \"active\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compact\": \"coun\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1994-11-05\",\n \"dateOfIssuance\": \"2425-09-15\",\n \"dateOfRenewal\": \"1310-11-08\",\n \"dateOfUpdate\": \"\",\n \"history\": [\n {\n \"compact\": \"aslp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"ms\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"2993-03-27\",\n \"dateOfIssuance\": \"2072-10-31\",\n \"dateOfRenewal\": \"2114-11-25\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"mn\",\n \"privilegeId\": \"\",\n \"compact\": \"coun\",\n \"jurisdiction\": \"md\",\n \"type\": \"privilege\",\n \"providerId\": \"4d5725cf-ad64-45fe-b60c-2224209edc4a\",\n \"status\": \"active\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"lifting_encumbrance\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"speech-language pathologist\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"mn\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"la\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"1994-03-26\",\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"1228-07-13\",\n \"privilegeId\": \"\",\n \"providerId\": \"38f48d47-72b6-4acd-9e61-da8cd93b2ad3\",\n \"dateOfRenewal\": \"2374-01-04\",\n \"dateOfUpdate\": \"\",\n \"status\": \"active\"\n }\n },\n {\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"ak\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"2781-12-27\",\n \"dateOfIssuance\": \"1187-05-12\",\n \"dateOfRenewal\": \"2763-09-21\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"de\",\n \"privilegeId\": \"\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"mo\",\n \"type\": \"privilege\",\n \"providerId\": \"a821d267-5653-49bd-97a2-bfc59170f24c\",\n \"status\": \"inactive\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"deactivation\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"occupational therapy assistant\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"in\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"id\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"1819-11-12\",\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"2038-12-26\",\n \"privilegeId\": \"\",\n \"providerId\": \"ad9f72d8-19d3-4b8e-b7b7-618dfe5e9e58\",\n \"dateOfRenewal\": \"1113-11-05\",\n \"dateOfUpdate\": \"\",\n \"status\": \"active\"\n }\n }\n ],\n \"jurisdiction\": \"wa\",\n \"licenseJurisdiction\": \"vt\",\n \"licenseType\": \"speech-language pathologist\",\n \"privilegeId\": \"\",\n \"providerId\": \"90bc19aa-772d-44ad-9cd6-3983304f2328\",\n \"status\": \"active\",\n \"type\": \"privilege\",\n \"investigationStatus\": \"underInvestigation\",\n \"investigations\": [\n {\n \"compact\": \"coun\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"mi\",\n \"licenseType\": \"\",\n \"providerId\": \"2dc8351a-6416-47e7-9b6e-2a34491c71c7\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"coun\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"ri\",\n \"licenseType\": \"\",\n \"providerId\": \"fe1216ae-9c2c-4b40-8c29-1d6b42028e71\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"octp\",\n \"creationDate\": \"1061-12-31\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1895-11-19\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"pr\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"2712681d-bded-4d93-905c-69072cc9ad3e\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2321-10-30\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"1298-05-03\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2743-07-01\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"or\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"c166bbe2-1f54-44d6-87ec-baf461c0b04e\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2562-12-05\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n }\n ]\n },\n {\n \"administratorSetStatus\": \"inactive\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compact\": \"coun\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1763-12-31\",\n \"dateOfIssuance\": \"1282-02-31\",\n \"dateOfRenewal\": \"1775-03-02\",\n \"dateOfUpdate\": \"\",\n \"history\": [\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"me\",\n \"previous\": {\n \"administratorSetStatus\": \"active\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1299-03-12\",\n \"dateOfIssuance\": \"2556-09-24\",\n \"dateOfRenewal\": \"2525-12-31\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"nc\",\n \"privilegeId\": \"\",\n \"compact\": \"aslp\",\n \"jurisdiction\": \"sc\",\n \"type\": \"privilege\",\n \"providerId\": \"fcee0893-045b-4e62-993b-2e1a0d5b9a6a\",\n \"status\": \"active\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"deactivation\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"licensed professional counselor\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"or\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"oh\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"2189-04-21\",\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"2561-09-02\",\n \"privilegeId\": \"\",\n \"providerId\": \"0e368060-5ad8-4526-a4d4-b3bb0ae375ee\",\n \"dateOfRenewal\": \"1197-12-28\",\n \"dateOfUpdate\": \"\",\n \"status\": \"active\"\n }\n },\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"nc\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"2675-12-14\",\n \"dateOfIssuance\": \"2405-08-11\",\n \"dateOfRenewal\": \"2288-09-14\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"az\",\n \"privilegeId\": \"\",\n \"compact\": \"coun\",\n \"jurisdiction\": \"hi\",\n \"type\": \"privilege\",\n \"providerId\": \"b442dc00-b160-49c2-9e00-adc648fe8e78\",\n \"status\": \"inactive\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"licenseDeactivation\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"occupational therapy assistant\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"md\",\n \"compact\": \"aslp\",\n \"jurisdiction\": \"ms\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"1632-10-06\",\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"2989-04-09\",\n \"privilegeId\": \"\",\n \"providerId\": \"f3981490-3938-4976-b957-7123142c46de\",\n \"dateOfRenewal\": \"1356-01-30\",\n \"dateOfUpdate\": \"\",\n \"status\": \"inactive\"\n }\n }\n ],\n \"jurisdiction\": \"ky\",\n \"licenseJurisdiction\": \"fl\",\n \"licenseType\": \"occupational therapist\",\n \"privilegeId\": \"\",\n \"providerId\": \"11ec1f36-39ef-411d-9ed6-0218a17bde45\",\n \"status\": \"inactive\",\n \"type\": \"privilege\",\n \"investigationStatus\": \"underInvestigation\",\n \"investigations\": [\n {\n \"compact\": \"aslp\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"va\",\n \"licenseType\": \"\",\n \"providerId\": \"f78a6f18-b3bd-4937-9a6c-b48d88245048\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"coun\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"oh\",\n \"licenseType\": \"\",\n \"providerId\": \"bf956261-d85b-4589-9cdb-446467415ef6\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"1572-08-30\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1663-10-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"nd\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"edea6027-5ae7-4c1f-a412-a29bed40d673\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1688-10-12\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"octp\",\n \"creationDate\": \"1780-08-11\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2812-12-13\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"al\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"f488d06a-d37c-4736-ba3a-c9d02cead4fa\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2996-12-11\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"providerId\": \"9b5458d5-9c52-41cd-b3b1-95f5c4affc2c\",\n \"type\": \"provider\",\n \"npi\": \"2535279913\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1940-02-02\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"currentHomeJurisdiction\": \"in\",\n \"ssnLastFour\": \"5671\",\n \"licenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"compactConnectRegisteredEmailAddress\": \"\"\n}", "code": 200, "cookie": [], "header": [ @@ -3734,7 +4430,7 @@ "value": "application/json" } ], - "id": "ef115f6d-e0a3-422d-adda-71e0a169c54e", + "id": "2e29babb-949f-4960-b5cd-e641b2923e21", "name": "200 response", "originalRequest": { "body": {}, @@ -3775,7 +4471,7 @@ "item": [ { "event": [], - "id": "8a6a586e-c161-4748-bdfa-8bcb5a734955", + "id": "154faaa4-1c24-44e2-869e-f2862cca561c", "name": "/v1/provider-users/me/email", "protocolProfileBehavior": { "disableBodyPruning": true @@ -3830,7 +4526,7 @@ "value": "application/json" } ], - "id": "f7f9ab26-3204-49c3-9253-fd7381331dde", + "id": "334aee2c-a6ef-48a3-acf9-695813214900", "name": "200 response", "originalRequest": { "body": { @@ -3885,7 +4581,7 @@ "item": [ { "event": [], - "id": "a43d9841-7ee0-41c4-a364-04637f05e883", + "id": "1a449848-b642-4d2b-91f3-bce7b7f8f0f8", "name": "/v1/provider-users/me/email/verify", "protocolProfileBehavior": { "disableBodyPruning": true @@ -3899,7 +4595,7 @@ "language": "json" } }, - "raw": "{\n \"verificationCode\": \"4687\"\n}" + "raw": "{\n \"verificationCode\": \"6671\"\n}" }, "description": {}, "header": [ @@ -3941,7 +4637,7 @@ "value": "application/json" } ], - "id": "61562898-53a1-4020-b719-a20260bbdaca", + "id": "63d859da-d627-49e6-84b5-0f85687795b4", "name": "200 response", "originalRequest": { "body": { @@ -3952,7 +4648,7 @@ "language": "json" } }, - "raw": "{\n \"verificationCode\": \"4687\"\n}" + "raw": "{\n \"verificationCode\": \"6671\"\n}" }, "header": [ { @@ -4003,7 +4699,7 @@ "item": [ { "event": [], - "id": "a746e45a-0c44-4899-abbe-7521954345e2", + "id": "0e3cf433-7bf5-4222-a18c-e7370b5b0af6", "name": "/v1/provider-users/me/home-jurisdiction", "protocolProfileBehavior": { "disableBodyPruning": true @@ -4017,7 +4713,7 @@ "language": "json" } }, - "raw": "{\n \"jurisdiction\": \"in\"\n}" + "raw": "{\n \"jurisdiction\": \"ia\"\n}" }, "description": {}, "header": [ @@ -4058,7 +4754,7 @@ "value": "application/json" } ], - "id": "d65672bf-7766-4a37-b426-7ff948be4d38", + "id": "b3a68e61-c492-42b9-a62d-d2c3ffe4caac", "name": "200 response", "originalRequest": { "body": { @@ -4069,7 +4765,7 @@ "language": "json" } }, - "raw": "{\n \"jurisdiction\": \"in\"\n}" + "raw": "{\n \"jurisdiction\": \"ia\"\n}" }, "header": [ { @@ -4128,7 +4824,7 @@ "item": [ { "event": [], - "id": "7f40a1a9-5d3e-4aa5-9993-4c9405cab673", + "id": "a5573123-c770-472d-aea9-e0fdc266d466", "name": "/v1/provider-users/me/jurisdiction/:jurisdiction/licenseType/:licenseType/history", "protocolProfileBehavior": { "disableBodyPruning": true @@ -4186,7 +4882,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"compact\": \"octp\",\n \"events\": [\n {\n \"createDate\": \"2076-04-30\",\n \"dateOfUpdate\": \"2073-12-31\",\n \"effectiveDate\": \"2942-07-31\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"deactivation\",\n \"note\": \"\"\n },\n {\n \"createDate\": \"1430-12-20\",\n \"dateOfUpdate\": \"2556-06-30\",\n \"effectiveDate\": \"2731-07-30\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"registration\",\n \"note\": \"\"\n }\n ],\n \"jurisdiction\": \"or\",\n \"licenseType\": \"occupational therapist\",\n \"privilegeId\": \"\",\n \"providerId\": \"629b1a4f-7c8c-47cf-a9be-aabf6bf4e69e\"\n}", + "body": "{\n \"compact\": \"octp\",\n \"events\": [\n {\n \"createDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"effectiveDate\": \"1230-11-04\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"registration\",\n \"note\": \"\"\n },\n {\n \"createDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"effectiveDate\": \"2425-11-31\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"registration\",\n \"note\": \"\"\n }\n ],\n \"jurisdiction\": \"in\",\n \"licenseType\": \"speech-language pathologist\",\n \"privilegeId\": \"\",\n \"providerId\": \"d89a7efe-5041-4146-96a6-3f76cf1296f6\"\n}", "code": 200, "cookie": [], "header": [ @@ -4195,7 +4891,7 @@ "value": "application/json" } ], - "id": "fe1903b1-2204-420b-9a09-4fec8b494042", + "id": "f187e63c-a81d-4da7-88fb-af6a9b178320", "name": "200 response", "originalRequest": { "body": {}, @@ -4256,7 +4952,7 @@ "item": [ { "event": [], - "id": "36e62486-f4b8-48a8-b460-4cc6623d29de", + "id": "257b92af-e83f-4677-9b8f-8bf35a995060", "name": "/v1/provider-users/me/military-affiliation", "protocolProfileBehavior": { "disableBodyPruning": true @@ -4302,7 +4998,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"affiliationType\": \"militaryMember\",\n \"dateOfUpdate\": \"2086-12-29\",\n \"dateOfUpload\": \"2560-01-09\",\n \"documentUploadFields\": [\n {\n \"fields\": {\n \"ex4\": \"\",\n \"aliqua73\": \"\"\n },\n \"url\": \"\"\n },\n {\n \"fields\": {\n \"tempor_2\": \"\"\n },\n \"url\": \"\"\n }\n ],\n \"status\": \"\",\n \"fileNames\": [\n \"\",\n \"\"\n ]\n}", + "body": "{\n \"affiliationType\": \"militaryMemberSpouse\",\n \"dateOfUpdate\": \"\",\n \"dateOfUpload\": \"1541-01-20\",\n \"documentUploadFields\": [\n {\n \"fields\": {\n \"key_0\": \"\",\n \"key_1\": \"\",\n \"key_2\": \"\"\n },\n \"url\": \"\"\n },\n {\n \"fields\": {\n \"key_0\": \"\"\n },\n \"url\": \"\"\n }\n ],\n \"status\": \"\",\n \"fileNames\": [\n \"\",\n \"\"\n ]\n}", "code": 200, "cookie": [], "header": [ @@ -4311,7 +5007,7 @@ "value": "application/json" } ], - "id": "52718662-2da1-4cc6-bc09-fb135f9c207f", + "id": "dd95b21d-fb30-4eb7-8368-9074c44dff0b", "name": "200 response", "originalRequest": { "body": { @@ -4363,7 +5059,7 @@ }, { "event": [], - "id": "4cd4a2e8-7354-42bf-a1fc-5244a1e64f54", + "id": "e6755b3d-9e1c-4153-872d-fd3529b1237e", "name": "/v1/provider-users/me/military-affiliation", "protocolProfileBehavior": { "disableBodyPruning": true @@ -4418,7 +5114,7 @@ "value": "application/json" } ], - "id": "eaa51e6c-c82d-4b31-9cf1-3e1bd59c3695", + "id": "00c89a32-e0f2-4a3f-97f2-9539bc3e468c", "name": "200 response", "originalRequest": { "body": { @@ -4479,7 +5175,7 @@ "item": [ { "event": [], - "id": "4e8a3d15-581b-42a2-a4f6-3ae1c869973e", + "id": "7a395ce2-3d57-4e53-8f28-bbafb685d43e", "name": "/v1/provider-users/registration", "protocolProfileBehavior": { "disableBodyPruning": true @@ -4496,7 +5192,7 @@ "language": "json" } }, - "raw": "{\n \"compact\": \"\",\n \"dob\": \"1502-06-08\",\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdiction\": \"or\",\n \"licenseType\": \"speech-language pathologist\",\n \"partialSocial\": \"\",\n \"token\": \"\"\n}" + "raw": "{\n \"compact\": \"\",\n \"dob\": \"2895-11-06\",\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdiction\": \"tn\",\n \"licenseType\": \"speech-language pathologist\",\n \"partialSocial\": \"\",\n \"token\": \"\"\n}" }, "description": {}, "header": [ @@ -4536,7 +5232,7 @@ "value": "application/json" } ], - "id": "5b0560e7-4616-45d2-8cd7-ade7c1d840e5", + "id": "148da7ca-0bc4-4cde-9d70-d6ebf187f8f2", "name": "200 response", "originalRequest": { "body": { @@ -4547,7 +5243,7 @@ "language": "json" } }, - "raw": "{\n \"compact\": \"\",\n \"dob\": \"1502-06-08\",\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdiction\": \"or\",\n \"licenseType\": \"speech-language pathologist\",\n \"partialSocial\": \"\",\n \"token\": \"\"\n}" + "raw": "{\n \"compact\": \"\",\n \"dob\": \"2895-11-06\",\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdiction\": \"tn\",\n \"licenseType\": \"speech-language pathologist\",\n \"partialSocial\": \"\",\n \"token\": \"\"\n}" }, "header": [ { @@ -4585,7 +5281,7 @@ "item": [ { "event": [], - "id": "5094b70a-c826-4cfc-a07d-8c96be7cf9ba", + "id": "1ae7f606-a7b2-40f3-a0ac-cfe256c74a18", "name": "/v1/provider-users/verifyRecovery", "protocolProfileBehavior": { "disableBodyPruning": true @@ -4602,7 +5298,7 @@ "language": "json" } }, - "raw": "{\n \"compact\": \"octp\",\n \"providerId\": \"22fa6d0d-cbb5-4ad7-809c-0e4f2e140a52\",\n \"recaptchaToken\": \"\",\n \"recoveryToken\": \"\"\n}" + "raw": "{\n \"compact\": \"octp\",\n \"providerId\": \"4ea47a54-7e2f-4daf-972c-3ccb4e0769d5\",\n \"recaptchaToken\": \"\",\n \"recoveryToken\": \"\"\n}" }, "description": {}, "header": [ @@ -4642,7 +5338,7 @@ "value": "application/json" } ], - "id": "8a5731e0-a67c-4e3f-8728-4f425f7c04bf", + "id": "19213d8a-2ffe-4638-9219-38ed3bf87dd8", "name": "200 response", "originalRequest": { "body": { @@ -4653,7 +5349,7 @@ "language": "json" } }, - "raw": "{\n \"compact\": \"octp\",\n \"providerId\": \"22fa6d0d-cbb5-4ad7-809c-0e4f2e140a52\",\n \"recaptchaToken\": \"\",\n \"recoveryToken\": \"\"\n}" + "raw": "{\n \"compact\": \"octp\",\n \"providerId\": \"4ea47a54-7e2f-4daf-972c-3ccb4e0769d5\",\n \"recaptchaToken\": \"\",\n \"recoveryToken\": \"\"\n}" }, "header": [ { @@ -4703,7 +5399,7 @@ "item": [ { "event": [], - "id": "e24dd7ed-c6ae-4544-a340-5ba18fa139ce", + "id": "a59f2d74-1c6e-4a97-8738-97cb1be17b72", "name": "/v1/public/compacts/:compact/jurisdictions", "protocolProfileBehavior": { "disableBodyPruning": true @@ -4760,7 +5456,7 @@ "value": "application/json" } ], - "id": "1516c3ad-a3b2-47f8-815b-602af9653e76", + "id": "72e88f34-ee26-44ac-a566-c164190a4b7e", "name": "200 response", "originalRequest": { "body": {}, @@ -4801,7 +5497,7 @@ "item": [ { "event": [], - "id": "f16b5d56-7250-423f-ae3c-1c0a9e474365", + "id": "41c7aac2-b45e-459d-a159-1c6fd4ae71b5", "name": "/v1/public/compacts/:compact/providers/query", "protocolProfileBehavior": { "disableBodyPruning": true @@ -4818,7 +5514,7 @@ "language": "json" } }, - "raw": "{\n \"query\": {\n \"providerId\": \"3f3c1b6f-4d02-4048-adef-7a8ec269307b\",\n \"jurisdiction\": \"co\",\n \"givenName\": \"\",\n \"familyName\": \"\"\n },\n \"pagination\": {\n \"lastKey\": \"\",\n \"pageSize\": \"\"\n },\n \"sorting\": {\n \"key\": \"dateOfUpdate\",\n \"direction\": \"descending\"\n }\n}" + "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}" }, "description": {}, "header": [ @@ -4863,7 +5559,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"pagination\": {\n \"prevLastKey\": {},\n \"lastKey\": {},\n \"pageSize\": \"\"\n },\n \"providers\": [\n {\n \"compact\": \"coun\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"licenseJurisdiction\": \"pa\",\n \"privilegeJurisdictions\": [\n \"md\",\n \"wa\"\n ],\n \"providerId\": \"014cf23e-586d-4830-96f6-498dd0bc17e4\",\n \"type\": \"provider\",\n \"npi\": \"1686568488\",\n \"middleName\": \"\",\n \"suffix\": \"\",\n \"currentHomeJurisdiction\": \"nv\",\n \"dateOfUpdate\": \"1336-11-30\"\n },\n {\n \"compact\": \"octp\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"licenseJurisdiction\": \"md\",\n \"privilegeJurisdictions\": [\n \"mt\",\n \"ny\"\n ],\n \"providerId\": \"154db0c4-a576-4ff9-9918-0f8e34b6766a\",\n \"type\": \"provider\",\n \"npi\": \"9316133384\",\n \"middleName\": \"\",\n \"suffix\": \"\",\n \"currentHomeJurisdiction\": \"or\",\n \"dateOfUpdate\": \"1296-10-26\"\n }\n ],\n \"query\": {\n \"providerId\": \"351f5560-8387-4142-b51c-a66f228464fa\",\n \"jurisdiction\": \"hi\",\n \"givenName\": \"\",\n \"familyName\": \"\"\n },\n \"sorting\": {\n \"key\": \"familyName\",\n \"direction\": \"ascending\"\n }\n}", + "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}", "code": 200, "cookie": [], "header": [ @@ -4872,7 +5568,7 @@ "value": "application/json" } ], - "id": "0e2f42bf-3c0b-48b1-8319-cb6e4a8b3a04", + "id": "8997fc38-2c70-4bc4-a3bc-ebe512fd7df8", "name": "200 response", "originalRequest": { "body": { @@ -4883,7 +5579,7 @@ "language": "json" } }, - "raw": "{\n \"query\": {\n \"providerId\": \"3f3c1b6f-4d02-4048-adef-7a8ec269307b\",\n \"jurisdiction\": \"co\",\n \"givenName\": \"\",\n \"familyName\": \"\"\n },\n \"pagination\": {\n \"lastKey\": \"\",\n \"pageSize\": \"\"\n },\n \"sorting\": {\n \"key\": \"dateOfUpdate\",\n \"direction\": \"descending\"\n }\n}" + "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}" }, "header": [ { @@ -4924,7 +5620,7 @@ "item": [ { "event": [], - "id": "28896e40-701a-408a-a09f-5c281d5df214", + "id": "591e37e5-f41b-43f7-ab3b-275447a86d24", "name": "/v1/public/compacts/:compact/providers/:providerId", "protocolProfileBehavior": { "disableBodyPruning": true @@ -4983,7 +5679,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"1011-08-30\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"licenseJurisdiction\": \"pr\",\n \"privilegeJurisdictions\": [\n \"co\",\n \"ia\"\n ],\n \"providerId\": \"f455e628-8579-49a3-ae90-7095f55097a5\",\n \"type\": \"provider\",\n \"privileges\": [\n {\n \"administratorSetStatus\": \"active\",\n \"compact\": \"coun\",\n \"dateOfExpiration\": \"1565-02-09\",\n \"dateOfIssuance\": \"2336-05-01\",\n \"dateOfRenewal\": \"2666-11-31\",\n \"dateOfUpdate\": \"2639-02-19\",\n \"jurisdiction\": \"la\",\n \"licenseJurisdiction\": \"al\",\n \"licenseType\": \"licensed professional counselor\",\n \"privilegeId\": \"\",\n \"providerId\": \"e3e54276-58ab-4e11-9212-135f2098ac51\",\n \"status\": \"inactive\",\n \"type\": \"privilege\",\n \"history\": [\n {\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"2396-05-06\",\n \"jurisdiction\": \"ma\",\n \"licenseType\": \"speech-language pathologist\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"1229-10-08\",\n \"dateOfIssuance\": \"2576-11-24\",\n \"dateOfRenewal\": \"2625-10-04\",\n \"dateOfUpdate\": \"1013-10-10\",\n \"licenseJurisdiction\": \"wv\",\n \"privilegeId\": \"\"\n },\n \"providerId\": \"ed6b9805-1800-4118-8330-082c7c1b6a8b\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"emailChange\",\n \"updatedValues\": {\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"1318-05-03\",\n \"licenseJurisdiction\": \"pr\",\n \"privilegeId\": \"\",\n \"dateOfRenewal\": \"2761-02-30\",\n \"dateOfIssuance\": \"2043-12-09\",\n \"dateOfUpdate\": \"1009-06-23\"\n }\n },\n {\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"1608-04-23\",\n \"jurisdiction\": \"de\",\n \"licenseType\": \"audiologist\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"2155-11-15\",\n \"dateOfIssuance\": \"1685-12-30\",\n \"dateOfRenewal\": \"2371-06-04\",\n \"dateOfUpdate\": \"1882-02-09\",\n \"licenseJurisdiction\": \"mn\",\n \"privilegeId\": \"\"\n },\n \"providerId\": \"d7af652a-c11f-41d0-b91f-d4a28d9c3014\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"emailChange\",\n \"updatedValues\": {\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"2655-12-09\",\n \"licenseJurisdiction\": \"mn\",\n \"privilegeId\": \"\",\n \"dateOfRenewal\": \"2906-01-05\",\n \"dateOfIssuance\": \"1568-07-06\",\n \"dateOfUpdate\": \"2297-07-31\"\n }\n }\n ],\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"octp\",\n \"creationDate\": \"1320-04-20\",\n \"dateOfUpdate\": \"2521-08-07\",\n \"effectiveStartDate\": \"2890-10-31\",\n \"jurisdiction\": \"ut\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"9715cd73-392a-4fce-a15c-5bf0a56bb2ef\",\n \"type\": \"adverseAction\",\n \"effectiveLiftDate\": \"1701-10-26\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"2026-07-06\",\n \"dateOfUpdate\": \"1536-07-30\",\n \"effectiveStartDate\": \"1831-03-22\",\n \"jurisdiction\": \"ca\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"e2a2a753-3c70-488f-8b44-3416feecb150\",\n \"type\": \"adverseAction\",\n \"effectiveLiftDate\": \"1866-10-04\"\n }\n ]\n },\n {\n \"administratorSetStatus\": \"active\",\n \"compact\": \"aslp\",\n \"dateOfExpiration\": \"1327-05-13\",\n \"dateOfIssuance\": \"1552-05-04\",\n \"dateOfRenewal\": \"1094-04-31\",\n \"dateOfUpdate\": \"2694-10-31\",\n \"jurisdiction\": \"fl\",\n \"licenseJurisdiction\": \"ct\",\n \"licenseType\": \"licensed professional counselor\",\n \"privilegeId\": \"\",\n \"providerId\": \"6e7132e4-a6c9-4c28-978f-efdba89a1262\",\n \"status\": \"inactive\",\n \"type\": \"privilege\",\n \"history\": [\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"1757-10-14\",\n \"jurisdiction\": \"sd\",\n \"licenseType\": \"occupational therapy assistant\",\n \"previous\": {\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"2915-08-30\",\n \"dateOfIssuance\": \"1938-10-06\",\n \"dateOfRenewal\": \"1570-12-30\",\n \"dateOfUpdate\": \"1805-01-31\",\n \"licenseJurisdiction\": \"in\",\n \"privilegeId\": \"\"\n },\n \"providerId\": \"09a82535-f4ab-48c7-84e4-14c518caea66\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"renewal\",\n \"updatedValues\": {\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"2935-04-04\",\n \"licenseJurisdiction\": \"nd\",\n \"privilegeId\": \"\",\n \"dateOfRenewal\": \"2766-10-30\",\n \"dateOfIssuance\": \"1195-12-08\",\n \"dateOfUpdate\": \"1839-11-02\"\n }\n },\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"2929-07-08\",\n \"jurisdiction\": \"ct\",\n \"licenseType\": \"occupational therapist\",\n \"previous\": {\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"2569-07-13\",\n \"dateOfIssuance\": \"1931-10-20\",\n \"dateOfRenewal\": \"2770-07-05\",\n \"dateOfUpdate\": \"1520-08-30\",\n \"licenseJurisdiction\": \"ar\",\n \"privilegeId\": \"\"\n },\n \"providerId\": \"22af5406-8d26-498c-b2c7-7d4f95605a3a\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"expiration\",\n \"updatedValues\": {\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"2799-10-09\",\n \"licenseJurisdiction\": \"tn\",\n \"privilegeId\": \"\",\n \"dateOfRenewal\": \"1095-02-08\",\n \"dateOfIssuance\": \"1662-12-15\",\n \"dateOfUpdate\": \"1377-11-30\"\n }\n }\n ],\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"1971-08-07\",\n \"dateOfUpdate\": \"2920-12-23\",\n \"effectiveStartDate\": \"1758-09-15\",\n \"jurisdiction\": \"la\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"05b0846e-bab4-4974-b1f0-9a5236ede55b\",\n \"type\": \"adverseAction\",\n \"effectiveLiftDate\": \"1609-08-05\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"aslp\",\n \"creationDate\": \"1850-04-06\",\n \"dateOfUpdate\": \"2557-06-06\",\n \"effectiveStartDate\": \"2693-05-09\",\n \"jurisdiction\": \"az\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"8bc074f3-05b2-4d33-b354-93c16d359060\",\n \"type\": \"adverseAction\",\n \"effectiveLiftDate\": \"2710-10-30\"\n }\n ]\n }\n ],\n \"npi\": \"3448942259\",\n \"suffix\": \"\",\n \"currentHomeJurisdiction\": \"unknown\",\n \"middleName\": \"\"\n}", + "body": "{\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"licenseJurisdiction\": \"al\",\n \"privilegeJurisdictions\": [\n \"ct\",\n \"nc\"\n ],\n \"providerId\": \"b8e9a88a-6649-48c9-9c4d-39a0256d5dde\",\n \"type\": \"provider\",\n \"privileges\": [\n {\n \"administratorSetStatus\": \"inactive\",\n \"compact\": \"octp\",\n \"dateOfExpiration\": \"1986-10-20\",\n \"dateOfIssuance\": \"1662-10-30\",\n \"dateOfRenewal\": \"2946-10-06\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"az\",\n \"licenseJurisdiction\": \"ct\",\n \"licenseType\": \"audiologist\",\n \"privilegeId\": \"\",\n \"providerId\": \"3b950d69-722f-4c6a-8d96-f988b8a8fad0\",\n \"status\": \"inactive\",\n \"type\": \"privilege\",\n \"history\": [\n {\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"ct\",\n \"licenseType\": \"licensed professional counselor\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"2147-05-30\",\n \"dateOfIssuance\": \"1588-06-19\",\n \"dateOfRenewal\": \"2566-04-30\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"ny\",\n \"privilegeId\": \"\"\n },\n \"providerId\": \"02e716d2-a904-4f81-8c22-8bfdebb4bec2\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"licenseDeactivation\",\n \"updatedValues\": {\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"2930-09-23\",\n \"licenseJurisdiction\": \"nm\",\n \"privilegeId\": \"\",\n \"dateOfRenewal\": \"2362-09-15\",\n \"dateOfIssuance\": \"2685-12-30\",\n \"dateOfUpdate\": \"\"\n }\n },\n {\n \"compact\": \"aslp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"ky\",\n \"licenseType\": \"occupational therapy assistant\",\n \"previous\": {\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"1953-10-07\",\n \"dateOfIssuance\": \"2767-07-04\",\n \"dateOfRenewal\": \"1924-05-31\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"dc\",\n \"privilegeId\": \"\"\n },\n \"providerId\": \"9867acc2-b67e-43fa-addd-4432e61d6be9\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"registration\",\n \"updatedValues\": {\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"1648-11-14\",\n \"licenseJurisdiction\": \"la\",\n \"privilegeId\": \"\",\n \"dateOfRenewal\": \"2787-03-16\",\n \"dateOfIssuance\": \"2519-09-24\",\n \"dateOfUpdate\": \"\"\n }\n }\n ],\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"aslp\",\n \"creationDate\": \"1625-10-31\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1012-11-28\",\n \"jurisdiction\": \"nm\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"e06b09b2-88aa-421c-83e3-22297d0f8de4\",\n \"type\": \"adverseAction\",\n \"effectiveLiftDate\": \"2114-10-03\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"octp\",\n \"creationDate\": \"2560-11-14\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1129-02-10\",\n \"jurisdiction\": \"ut\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"ba43fcc4-1327-4e36-a033-7380ee054b98\",\n \"type\": \"adverseAction\",\n \"effectiveLiftDate\": \"2025-05-08\"\n }\n ]\n },\n {\n \"administratorSetStatus\": \"active\",\n \"compact\": \"coun\",\n \"dateOfExpiration\": \"1991-06-30\",\n \"dateOfIssuance\": \"1735-11-23\",\n \"dateOfRenewal\": \"1886-12-22\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"sd\",\n \"licenseJurisdiction\": \"in\",\n \"licenseType\": \"audiologist\",\n \"privilegeId\": \"\",\n \"providerId\": \"1cf5fc6f-c5d3-4324-ba23-b164fc54ee2c\",\n \"status\": \"active\",\n \"type\": \"privilege\",\n \"history\": [\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"sc\",\n \"licenseType\": \"occupational therapist\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"2252-07-16\",\n \"dateOfIssuance\": \"2133-12-30\",\n \"dateOfRenewal\": \"1267-10-14\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"tn\",\n \"privilegeId\": \"\"\n },\n \"providerId\": \"348f1664-5e02-42d7-a17f-0e493a567cf2\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"homeJurisdictionChange\",\n \"updatedValues\": {\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"1846-02-17\",\n \"licenseJurisdiction\": \"nh\",\n \"privilegeId\": \"\",\n \"dateOfRenewal\": \"1052-04-04\",\n \"dateOfIssuance\": \"1717-10-30\",\n \"dateOfUpdate\": \"\"\n }\n },\n {\n \"compact\": \"aslp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"tn\",\n \"licenseType\": \"occupational therapist\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"1435-11-01\",\n \"dateOfIssuance\": \"1058-12-02\",\n \"dateOfRenewal\": \"1021-07-10\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"ak\",\n \"privilegeId\": \"\"\n },\n \"providerId\": \"863e4569-58f7-4fa4-a7c7-2e97779e1cd9\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"registration\",\n \"updatedValues\": {\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"1516-11-31\",\n \"licenseJurisdiction\": \"de\",\n \"privilegeId\": \"\",\n \"dateOfRenewal\": \"1017-09-30\",\n \"dateOfIssuance\": \"2217-12-20\",\n \"dateOfUpdate\": \"\"\n }\n }\n ],\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"aslp\",\n \"creationDate\": \"1258-11-25\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2168-12-30\",\n \"jurisdiction\": \"il\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"4487cfc6-97ad-46d7-a942-433ce98423bc\",\n \"type\": \"adverseAction\",\n \"effectiveLiftDate\": \"1268-09-30\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"2019-04-04\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1250-06-30\",\n \"jurisdiction\": \"mt\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"9ca3a75b-ca10-47db-8884-b9f1df71ae62\",\n \"type\": \"adverseAction\",\n \"effectiveLiftDate\": \"2878-11-30\"\n }\n ]\n }\n ],\n \"npi\": \"1820740143\",\n \"suffix\": \"\",\n \"currentHomeJurisdiction\": \"pr\",\n \"middleName\": \"\"\n}", "code": 200, "cookie": [], "header": [ @@ -4992,7 +5688,7 @@ "value": "application/json" } ], - "id": "d41d07cc-a8a4-49f0-bfe7-c38282571ba4", + "id": "78220f24-ba91-48b4-bca6-26cc84183cd3", "name": "200 response", "originalRequest": { "body": {}, @@ -5040,7 +5736,7 @@ "item": [ { "event": [], - "id": "fb23515f-7944-41aa-9fde-18c4c1e02b8e", + "id": "fd716311-dce7-4a37-979f-304a141f0b43", "name": "/v1/public/compacts/:compact/providers/:providerId/jurisdiction/:jurisdiction/licenseType/:licenseType/history", "protocolProfileBehavior": { "disableBodyPruning": true @@ -5124,7 +5820,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"compact\": \"octp\",\n \"events\": [\n {\n \"createDate\": \"2076-04-30\",\n \"dateOfUpdate\": \"2073-12-31\",\n \"effectiveDate\": \"2942-07-31\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"deactivation\",\n \"note\": \"\"\n },\n {\n \"createDate\": \"1430-12-20\",\n \"dateOfUpdate\": \"2556-06-30\",\n \"effectiveDate\": \"2731-07-30\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"registration\",\n \"note\": \"\"\n }\n ],\n \"jurisdiction\": \"or\",\n \"licenseType\": \"occupational therapist\",\n \"privilegeId\": \"\",\n \"providerId\": \"629b1a4f-7c8c-47cf-a9be-aabf6bf4e69e\"\n}", + "body": "{\n \"compact\": \"octp\",\n \"events\": [\n {\n \"createDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"effectiveDate\": \"1230-11-04\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"registration\",\n \"note\": \"\"\n },\n {\n \"createDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"effectiveDate\": \"2425-11-31\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"registration\",\n \"note\": \"\"\n }\n ],\n \"jurisdiction\": \"in\",\n \"licenseType\": \"speech-language pathologist\",\n \"privilegeId\": \"\",\n \"providerId\": \"d89a7efe-5041-4146-96a6-3f76cf1296f6\"\n}", "code": 200, "cookie": [], "header": [ @@ -5133,7 +5829,7 @@ "value": "application/json" } ], - "id": "732e73a4-f5d3-4ef7-bff3-315de5c5a612", + "id": "231c170a-79c9-4426-9ab8-9a76fb6dbd78", "name": "200 response", "originalRequest": { "body": {}, @@ -5195,6 +5891,114 @@ } ], "name": "compacts" + }, + { + "description": "", + "item": [ + { + "description": "", + "item": [ + { + "event": [], + "id": "52dc84cd-5caa-447f-a9c6-0f67edb88947", + "name": "/v1/public/jurisdictions/live", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "noauth" + }, + "body": {}, + "description": {}, + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "name": "/v1/public/jurisdictions/live", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "public", + "jurisdictions", + "live" + ], + "query": [ + { + "description": { + "content": "", + "type": "text/plain" + }, + "disabled": false, + "key": "compact", + "value": "" + } + ], + "variable": [] + } + }, + "response": [ + { + "_postman_previewlanguage": "json", + "body": "{\n \"key_0\": [\n \"ut\",\n \"wv\"\n ]\n}", + "code": 200, + "cookie": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "id": "c7228003-70c9-4263-8f12-b132ff502951", + "name": "200 response", + "originalRequest": { + "body": {}, + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "public", + "jurisdictions", + "live" + ], + "query": [ + { + "description": { + "content": "", + "type": "text/plain" + }, + "disabled": false, + "key": "compact", + "value": "" + } + ], + "variable": [] + } + }, + "status": "OK" + } + ] + } + ], + "name": "live" + } + ], + "name": "jurisdictions" } ], "name": "public" @@ -5207,7 +6011,7 @@ "item": [ { "event": [], - "id": "e2274233-989e-4994-9327-60283ec69127", + "id": "77058b43-efa8-4c30-bba6-f9d1edf387db", "name": "/v1/purchases/privileges", "protocolProfileBehavior": { "disableBodyPruning": true @@ -5221,7 +6025,7 @@ "language": "json" } }, - "raw": "{\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"3\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"09\"\n }\n ],\n \"licenseType\": \"licensed professional counselor\",\n \"orderInformation\": {\n \"opaqueData\": {\n \"dataDescriptor\": \"\",\n \"dataValue\": \"\"\n }\n },\n \"selectedJurisdictions\": [\n \"vi\",\n \"wi\"\n ]\n}" + "raw": "{\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"978179715\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"96663263\"\n }\n ],\n \"licenseType\": \"occupational therapist\",\n \"orderInformation\": {\n \"opaqueData\": {\n \"dataDescriptor\": \"\",\n \"dataValue\": \"\"\n }\n },\n \"selectedJurisdictions\": [\n \"vt\",\n \"mt\"\n ]\n}" }, "description": {}, "header": [ @@ -5261,7 +6065,7 @@ "value": "application/json" } ], - "id": "e724c3e3-1c30-4896-a034-5d211b6589b6", + "id": "6d02f8ec-d2a0-43e6-a831-016fadbf70fc", "name": "200 response", "originalRequest": { "body": { @@ -5272,7 +6076,7 @@ "language": "json" } }, - "raw": "{\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"3\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"09\"\n }\n ],\n \"licenseType\": \"licensed professional counselor\",\n \"orderInformation\": {\n \"opaqueData\": {\n \"dataDescriptor\": \"\",\n \"dataValue\": \"\"\n }\n },\n \"selectedJurisdictions\": [\n \"vi\",\n \"wi\"\n ]\n}" + "raw": "{\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"978179715\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"96663263\"\n }\n ],\n \"licenseType\": \"occupational therapist\",\n \"orderInformation\": {\n \"opaqueData\": {\n \"dataDescriptor\": \"\",\n \"dataValue\": \"\"\n }\n },\n \"selectedJurisdictions\": [\n \"vt\",\n \"mt\"\n ]\n}" }, "header": [ { @@ -5315,7 +6119,7 @@ "item": [ { "event": [], - "id": "d1e08ac0-f9f4-4b94-9245-9e316a8a6d2f", + "id": "44420427-e1a1-4f6a-8c55-c446bd397150", "name": "/v1/purchases/privileges/options", "protocolProfileBehavior": { "disableBodyPruning": true @@ -5357,7 +6161,7 @@ "value": "application/json" } ], - "id": "45d7dfef-4150-431d-af45-fb2d7b1e95e4", + "id": "0211181c-f18a-4ee5-974a-3ffd066af744", "name": "200 response", "originalRequest": { "body": {}, @@ -5411,7 +6215,7 @@ "item": [ { "event": [], - "id": "c34ac6d9-59c9-4334-a2dc-271d84ee101c", + "id": "eaef01d3-0498-4681-8a8b-8f61c1fa42bb", "name": "/v1/staff-users/me", "protocolProfileBehavior": { "disableBodyPruning": true @@ -5443,7 +6247,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"officia_25\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"exercitation_2d6\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"proident8e6\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"Duis_912\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"non9\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"fugiat_\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"inactive\",\n \"userId\": \"\"\n}", + "body": "{\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"active\",\n \"userId\": \"\"\n}", "code": 200, "cookie": [], "header": [ @@ -5461,7 +6265,7 @@ "value": "" } ], - "id": "ba7b63ba-648e-4c8f-a60e-e232d6eaca77", + "id": "50509350-3bf2-4d40-9a12-ca779d159cd6", "name": "200 response", "originalRequest": { "body": {}, @@ -5506,7 +6310,7 @@ "value": "application/json" } ], - "id": "cdc8dc32-b42a-49cb-ad48-5d294fe0d448", + "id": "3d2c2a65-b4d5-4c77-aadc-794c9e50f644", "name": "404 response", "originalRequest": { "body": {}, @@ -5544,7 +6348,7 @@ }, { "event": [], - "id": "7a60e7d4-3d8d-4e02-819c-cee4c0347fbe", + "id": "0c887909-602d-46f4-9d1c-b55d4c08c53e", "name": "/v1/staff-users/me", "protocolProfileBehavior": { "disableBodyPruning": true @@ -5589,7 +6393,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"officia_25\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"exercitation_2d6\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"proident8e6\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"Duis_912\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"non9\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"fugiat_\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"inactive\",\n \"userId\": \"\"\n}", + "body": "{\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"active\",\n \"userId\": \"\"\n}", "code": 200, "cookie": [], "header": [ @@ -5607,7 +6411,7 @@ "value": "" } ], - "id": "fd1209c9-355a-40fe-8532-3b9943897184", + "id": "fec1dfd7-6449-47c0-a6b6-d0757a6f4f48", "name": "200 response", "originalRequest": { "body": { @@ -5665,7 +6469,7 @@ "value": "application/json" } ], - "id": "5def400a-26b7-4d02-a0e7-2359dc3c4fd7", + "id": "f6663f03-5203-4ec2-a071-fd93d1b627aa", "name": "404 response", "originalRequest": { "body": { diff --git a/backend/compact-connect/docs/postman/postman-collection.json b/backend/compact-connect/docs/postman/postman-collection.json index 199152544..90ab6b2f7 100644 --- a/backend/compact-connect/docs/postman/postman-collection.json +++ b/backend/compact-connect/docs/postman/postman-collection.json @@ -10,7 +10,7 @@ "type": "bearer" }, "info": { - "_postman_id": "5f66dede-44e7-4836-974c-7d95890c630e", + "_postman_id": "2dd3c79b-7848-4980-8615-72b6b4c2b4b7", "description": { "content": "", "type": "text/plain" @@ -410,7 +410,7 @@ "item": [ { "event": [], - "id": "2947be35-f9b4-426e-bd4c-d7d4c22d6805", + "id": "e4992458-08cd-4c8b-acbf-59af7b7e3a75", "name": "/v1/compacts/:compact/jurisdictions/:jurisdiction/licenses", "protocolProfileBehavior": { "disableBodyPruning": true @@ -424,7 +424,7 @@ "language": "json" } }, - "raw": "[\n {\n \"compactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2645-07-26\",\n \"dateOfExpiration\": \"2828-03-03\",\n \"dateOfIssuance\": \"2731-01-13\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"licenseStatus\": \"inactive\",\n \"licenseType\": \"speech-language pathologist\",\n \"ssn\": \"136-74-9894\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"4963053110\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+30127955274\",\n \"dateOfRenewal\": \"1892-11-26\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n },\n {\n \"compactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2074-12-30\",\n \"dateOfExpiration\": \"2405-01-30\",\n \"dateOfIssuance\": \"2351-10-03\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"occupational therapy assistant\",\n \"ssn\": \"003-96-8237\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"7623604602\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+79745125\",\n \"dateOfRenewal\": \"2956-08-31\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n]" + "raw": "[\n {\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"1607-11-17\",\n \"dateOfExpiration\": \"2885-05-26\",\n \"dateOfIssuance\": \"1146-03-30\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"licensed professional counselor\",\n \"ssn\": \"151-51-8414\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"7741340530\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+3849258700\",\n \"dateOfRenewal\": \"2882-02-09\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n },\n {\n \"compactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1479-12-06\",\n \"dateOfExpiration\": \"1919-12-10\",\n \"dateOfIssuance\": \"1300-04-29\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"licensed professional counselor\",\n \"ssn\": \"971-92-2380\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"6327468598\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+925079022127\",\n \"dateOfRenewal\": \"1269-03-19\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n]" }, "description": {}, "header": [ @@ -488,7 +488,7 @@ "value": "application/json" } ], - "id": "35a5a8a2-4609-407d-9d3f-222c66df1788", + "id": "a4a13d96-78a0-4ab6-8fb1-af82a1d3c500", "name": "200 response", "originalRequest": { "body": { @@ -499,7 +499,7 @@ "language": "json" } }, - "raw": "[\n {\n \"compactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2645-07-26\",\n \"dateOfExpiration\": \"2828-03-03\",\n \"dateOfIssuance\": \"2731-01-13\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"licenseStatus\": \"inactive\",\n \"licenseType\": \"speech-language pathologist\",\n \"ssn\": \"136-74-9894\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"4963053110\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+30127955274\",\n \"dateOfRenewal\": \"1892-11-26\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n },\n {\n \"compactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2074-12-30\",\n \"dateOfExpiration\": \"2405-01-30\",\n \"dateOfIssuance\": \"2351-10-03\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"occupational therapy assistant\",\n \"ssn\": \"003-96-8237\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"7623604602\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+79745125\",\n \"dateOfRenewal\": \"2956-08-31\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n]" + "raw": "[\n {\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"1607-11-17\",\n \"dateOfExpiration\": \"2885-05-26\",\n \"dateOfIssuance\": \"1146-03-30\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"licensed professional counselor\",\n \"ssn\": \"151-51-8414\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"7741340530\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+3849258700\",\n \"dateOfRenewal\": \"2882-02-09\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n },\n {\n \"compactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1479-12-06\",\n \"dateOfExpiration\": \"1919-12-10\",\n \"dateOfIssuance\": \"1300-04-29\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"licensed professional counselor\",\n \"ssn\": \"971-92-2380\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"6327468598\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+925079022127\",\n \"dateOfRenewal\": \"1269-03-19\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n]" }, "header": [ { @@ -540,7 +540,7 @@ }, { "_postman_previewlanguage": "json", - "body": "{\n \"message\": \"\",\n \"errors\": {\n \"key_0\": {\n \"key_0\": [\n \"\",\n \"\"\n ]\n },\n \"key_1\": {\n \"key_0\": [\n \"\",\n \"\"\n ],\n \"key_1\": [\n \"\",\n \"\"\n ]\n },\n \"key_2\": {\n \"key_0\": [\n \"\",\n \"\"\n ]\n },\n \"key_3\": {\n \"key_0\": [\n \"\",\n \"\"\n ]\n }\n }\n}", + "body": "{\n \"message\": \"\",\n \"errors\": {\n \"key_0\": {\n \"key_0\": [\n \"\",\n \"\"\n ],\n \"key_1\": [\n \"\",\n \"\"\n ]\n }\n }\n}", "code": 400, "cookie": [], "header": [ @@ -549,7 +549,7 @@ "value": "application/json" } ], - "id": "a69fcb9f-37f9-4c6d-a195-9ae23053d841", + "id": "a367f319-890c-4b07-82e6-f0159f7909a8", "name": "400 response", "originalRequest": { "body": { @@ -560,7 +560,7 @@ "language": "json" } }, - "raw": "[\n {\n \"compactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2645-07-26\",\n \"dateOfExpiration\": \"2828-03-03\",\n \"dateOfIssuance\": \"2731-01-13\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"licenseStatus\": \"inactive\",\n \"licenseType\": \"speech-language pathologist\",\n \"ssn\": \"136-74-9894\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"4963053110\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+30127955274\",\n \"dateOfRenewal\": \"1892-11-26\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n },\n {\n \"compactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2074-12-30\",\n \"dateOfExpiration\": \"2405-01-30\",\n \"dateOfIssuance\": \"2351-10-03\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"occupational therapy assistant\",\n \"ssn\": \"003-96-8237\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"7623604602\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+79745125\",\n \"dateOfRenewal\": \"2956-08-31\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n]" + "raw": "[\n {\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"1607-11-17\",\n \"dateOfExpiration\": \"2885-05-26\",\n \"dateOfIssuance\": \"1146-03-30\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"licensed professional counselor\",\n \"ssn\": \"151-51-8414\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"7741340530\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+3849258700\",\n \"dateOfRenewal\": \"2882-02-09\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n },\n {\n \"compactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1479-12-06\",\n \"dateOfExpiration\": \"1919-12-10\",\n \"dateOfIssuance\": \"1300-04-29\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"licensed professional counselor\",\n \"ssn\": \"971-92-2380\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"6327468598\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+925079022127\",\n \"dateOfRenewal\": \"1269-03-19\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n]" }, "header": [ { @@ -631,7 +631,7 @@ } } ], - "id": "7be4d7f8-5b9a-40af-b7eb-19ace4dbd635", + "id": "17440cb5-a8c9-4e7b-8660-6588d8a9d001", "name": "/v1/compacts/:compact/jurisdictions/:jurisdiction/licenses/bulk-upload", "protocolProfileBehavior": { "disableBodyPruning": true @@ -688,7 +688,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"upload\": {\n \"fields\": {\n \"key_0\": \"\"\n },\n \"url\": \"\"\n }\n}", + "body": "{\n \"upload\": {\n \"fields\": {\n \"key_0\": \"\",\n \"key_1\": \"\",\n \"key_2\": \"\"\n },\n \"url\": \"\"\n }\n}", "code": 200, "cookie": [], "header": [ @@ -697,7 +697,7 @@ "value": "application/json" } ], - "id": "86562271-fd3e-4b33-920a-67cb03e72f84", + "id": "1e851c00-2aab-4ab4-9e5a-0a2a05bc1bf7", "name": "200 response", "originalRequest": { "body": {}, @@ -751,7 +751,7 @@ "item": [ { "event": [], - "id": "96eeb21f-485f-4bae-a755-1c83d559b766", + "id": "7aff71f6-348b-440e-8fbc-bae6dff7732b", "name": "/v1/compacts/:compact/jurisdictions/:jurisdiction/providers/query", "protocolProfileBehavior": { "disableBodyPruning": true @@ -765,7 +765,7 @@ "language": "json" } }, - "raw": "{\n \"query\": {\n \"endDateTime\": \"1263-06-30T11:18:46Z\",\n \"startDateTime\": \"2179-10-19T23:39:33.39Z\"\n },\n \"pagination\": {\n \"lastKey\": \"\",\n \"pageSize\": \"\"\n },\n \"sorting\": {\n \"direction\": \"ascending\"\n }\n}" + "raw": "{\n \"query\": {\n \"endDateTime\": \"2768-12-31T21:50:21Z\",\n \"startDateTime\": \"1344-03-17T23:04:37Z\"\n },\n \"pagination\": {\n \"lastKey\": \"\",\n \"pageSize\": \"\"\n },\n \"sorting\": {\n \"direction\": \"ascending\"\n }\n}" }, "description": {}, "header": [ @@ -821,7 +821,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"pagination\": {\n \"prevLastKey\": {},\n \"lastKey\": {},\n \"pageSize\": \"\"\n },\n \"providers\": [\n {\n \"birthMonthDay\": \"17-32\",\n \"compact\": \"octp\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"2120-08-30\",\n \"dateOfUpdate\": \"1173-06-05\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"licenseJurisdiction\": \"wy\",\n \"licenseStatus\": \"active\",\n \"privilegeJurisdictions\": [\n \"md\",\n \"co\"\n ],\n \"providerId\": \"5d8ebac9-4e07-4542-8cd1-fe5e880c3faf\",\n \"type\": \"provider\",\n \"npi\": \"5345682984\",\n \"dateOfBirth\": \"2663-11-06\",\n \"suffix\": \"\",\n \"ssnLastFour\": \"9299\",\n \"middleName\": \"\",\n \"compactConnectRegisteredEmailAddress\": \"\"\n },\n {\n \"birthMonthDay\": \"09-21\",\n \"compact\": \"octp\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"1289-11-30\",\n \"dateOfUpdate\": \"2080-03-30\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"licenseJurisdiction\": \"il\",\n \"licenseStatus\": \"active\",\n \"privilegeJurisdictions\": [\n \"or\",\n \"ga\"\n ],\n \"providerId\": \"61431134-8e7c-4ef4-b17f-f5f553606b24\",\n \"type\": \"provider\",\n \"npi\": \"1773499258\",\n \"dateOfBirth\": \"1334-10-01\",\n \"suffix\": \"\",\n \"ssnLastFour\": \"8312\",\n \"middleName\": \"\",\n \"compactConnectRegisteredEmailAddress\": \"\"\n }\n ],\n \"sorting\": {\n \"direction\": \"descending\"\n }\n}", + "body": "{\n \"pagination\": {\n \"prevLastKey\": {},\n \"lastKey\": {},\n \"pageSize\": \"\"\n },\n \"providers\": [\n {\n \"birthMonthDay\": \"05-36\",\n \"compact\": \"octp\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"1378-07-14\",\n \"dateOfUpdate\": \"1918-09-24\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"licenseJurisdiction\": \"nh\",\n \"licenseStatus\": \"active\",\n \"privilegeJurisdictions\": [\n \"nd\",\n \"ma\"\n ],\n \"providerId\": \"1e754c66-bcb7-48d9-8b0b-12a108fb94fd\",\n \"type\": \"provider\",\n \"npi\": \"8783122207\",\n \"dateOfBirth\": \"2750-12-30\",\n \"suffix\": \"\",\n \"ssnLastFour\": \"5085\",\n \"middleName\": \"\",\n \"compactConnectRegisteredEmailAddress\": \"\"\n },\n {\n \"birthMonthDay\": \"19-02\",\n \"compact\": \"coun\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"1439-02-11\",\n \"dateOfUpdate\": \"1062-09-25\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"licenseJurisdiction\": \"mi\",\n \"licenseStatus\": \"active\",\n \"privilegeJurisdictions\": [\n \"sd\",\n \"ky\"\n ],\n \"providerId\": \"379ed99f-db30-41b5-88e3-369a153f1716\",\n \"type\": \"provider\",\n \"npi\": \"0274556733\",\n \"dateOfBirth\": \"1391-10-06\",\n \"suffix\": \"\",\n \"ssnLastFour\": \"8798\",\n \"middleName\": \"\",\n \"compactConnectRegisteredEmailAddress\": \"\"\n }\n ],\n \"sorting\": {\n \"direction\": \"ascending\"\n }\n}", "code": 200, "cookie": [], "header": [ @@ -830,7 +830,7 @@ "value": "application/json" } ], - "id": "fd157d64-b01d-4f79-ab23-69a99602f647", + "id": "614232ac-e418-4618-a5c8-acaf9aeef875", "name": "200 response", "originalRequest": { "body": { @@ -841,7 +841,7 @@ "language": "json" } }, - "raw": "{\n \"query\": {\n \"endDateTime\": \"1263-06-30T11:18:46Z\",\n \"startDateTime\": \"2179-10-19T23:39:33.39Z\"\n },\n \"pagination\": {\n \"lastKey\": \"\",\n \"pageSize\": \"\"\n },\n \"sorting\": {\n \"direction\": \"ascending\"\n }\n}" + "raw": "{\n \"query\": {\n \"endDateTime\": \"2768-12-31T21:50:21Z\",\n \"startDateTime\": \"1344-03-17T23:04:37Z\"\n },\n \"pagination\": {\n \"lastKey\": \"\",\n \"pageSize\": \"\"\n },\n \"sorting\": {\n \"direction\": \"ascending\"\n }\n}" }, "header": [ { @@ -891,7 +891,7 @@ "item": [ { "event": [], - "id": "bde6bd63-70ba-4a55-bfdf-a49075d23e64", + "id": "a454e304-f168-451f-8def-24a377fa6f44", "name": "/v1/compacts/:compact/jurisdictions/:jurisdiction/providers/:providerId", "protocolProfileBehavior": { "disableBodyPruning": true @@ -958,7 +958,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"privileges\": [\n {\n \"compact\": \"coun\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"1532-10-09\",\n \"dateOfIssuance\": \"2232-05-26\",\n \"dateOfRenewal\": \"2135-05-19\",\n \"dateOfUpdate\": \"2760-10-30\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdiction\": \"ct\",\n \"licenseJurisdiction\": \"ri\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"occupational therapist\",\n \"privilegeId\": \"\",\n \"providerId\": \"01bba77b-79c3-4692-bcac-55721dc440e7\",\n \"status\": \"inactive\",\n \"type\": \"statePrivilege\",\n \"homeAddressStreet2\": \"\",\n \"homeAddressStreet1\": \"\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"compactConnectRegisteredEmailAddress\": \"\",\n \"npi\": \"1296707478\",\n \"homeAddressPostalCode\": \"\",\n \"dateOfBirth\": \"2962-06-30\",\n \"ssnLastFour\": \"8929\",\n \"phoneNumber\": \"+97589140949\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n },\n {\n \"compact\": \"coun\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"2474-12-13\",\n \"dateOfIssuance\": \"1648-03-07\",\n \"dateOfRenewal\": \"2527-12-03\",\n \"dateOfUpdate\": \"2575-12-24\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdiction\": \"wa\",\n \"licenseJurisdiction\": \"wa\",\n \"licenseStatus\": \"inactive\",\n \"licenseType\": \"audiologist\",\n \"privilegeId\": \"\",\n \"providerId\": \"77d601ee-f28e-42ee-99a0-c086c6453645\",\n \"status\": \"inactive\",\n \"type\": \"statePrivilege\",\n \"homeAddressStreet2\": \"\",\n \"homeAddressStreet1\": \"\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"compactConnectRegisteredEmailAddress\": \"\",\n \"npi\": \"0576030936\",\n \"homeAddressPostalCode\": \"\",\n \"dateOfBirth\": \"2668-05-06\",\n \"ssnLastFour\": \"8289\",\n \"phoneNumber\": \"+2343846641\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n ],\n \"providerUIUrl\": \"\"\n}", + "body": "{\n \"privileges\": [\n {\n \"compact\": \"aslp\",\n \"compactEligibility\": \"eligible\",\n \"dateOfExpiration\": \"1744-10-04\",\n \"dateOfIssuance\": \"2547-11-19\",\n \"dateOfRenewal\": \"2433-11-03\",\n \"dateOfUpdate\": \"1383-11-12\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdiction\": \"ct\",\n \"licenseJurisdiction\": \"nj\",\n \"licenseStatus\": \"inactive\",\n \"licenseType\": \"licensed professional counselor\",\n \"privilegeId\": \"\",\n \"providerId\": \"339453dd-5ddf-4592-bf48-0491ee1dd127\",\n \"status\": \"inactive\",\n \"type\": \"statePrivilege\",\n \"homeAddressStreet2\": \"\",\n \"homeAddressStreet1\": \"\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"compactConnectRegisteredEmailAddress\": \"\",\n \"npi\": \"1692585889\",\n \"homeAddressPostalCode\": \"\",\n \"dateOfBirth\": \"1741-11-30\",\n \"ssnLastFour\": \"6801\",\n \"phoneNumber\": \"+7617633940581\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n },\n {\n \"compact\": \"octp\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"2460-10-30\",\n \"dateOfIssuance\": \"2105-02-30\",\n \"dateOfRenewal\": \"1126-02-27\",\n \"dateOfUpdate\": \"2918-08-30\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdiction\": \"ct\",\n \"licenseJurisdiction\": \"ca\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"speech-language pathologist\",\n \"privilegeId\": \"\",\n \"providerId\": \"e943dcb6-630b-4510-bba5-e6fb1bf7b688\",\n \"status\": \"inactive\",\n \"type\": \"statePrivilege\",\n \"homeAddressStreet2\": \"\",\n \"homeAddressStreet1\": \"\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"compactConnectRegisteredEmailAddress\": \"\",\n \"npi\": \"3740829736\",\n \"homeAddressPostalCode\": \"\",\n \"dateOfBirth\": \"1879-06-31\",\n \"ssnLastFour\": \"8866\",\n \"phoneNumber\": \"+59156261550\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n ],\n \"providerUIUrl\": \"\"\n}", "code": 200, "cookie": [], "header": [ @@ -967,7 +967,7 @@ "value": "application/json" } ], - "id": "c1a4cf7d-4e4e-44f7-a748-6205c0858200", + "id": "1ad41a6a-7de7-4bcc-8979-407410439cf2", "name": "200 response", "originalRequest": { "body": {}, diff --git a/backend/compact-connect/lambdas/nodejs/cognito-emails/lambda.ts b/backend/compact-connect/lambdas/nodejs/cognito-emails/lambda.ts index b64ad072f..b1c5542df 100644 --- a/backend/compact-connect/lambdas/nodejs/cognito-emails/lambda.ts +++ b/backend/compact-connect/lambdas/nodejs/cognito-emails/lambda.ts @@ -81,7 +81,7 @@ export class Lambda implements LambdaInterface { * @returns Modified event with custom email message and subject */ @logger.injectLambdaContext({ resetKeys: true }) - public async handler(event: CognitoCustomMessageEvent, context: Context): Promise { + public async handler(event: CognitoCustomMessageEvent, _context: Context): Promise { logger.info('Processing Cognito custom message event', { triggerSource: event.triggerSource, userPoolId: event.userPoolId, 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 3298496c3..c8672c9da 100644 --- a/backend/compact-connect/lambdas/nodejs/email-notification-service/lambda.ts +++ b/backend/compact-connect/lambdas/nodejs/email-notification-service/lambda.ts @@ -8,7 +8,7 @@ import { Context } from 'aws-lambda'; import { EnvironmentVariablesService } from '../lib/environment-variables-service'; import { CompactConfigurationClient } from '../lib/compact-configuration-client'; import { JurisdictionClient } from '../lib/jurisdiction-client'; -import { EmailNotificationService, EncumbranceNotificationService } from '../lib/email'; +import { EmailNotificationService, EncumbranceNotificationService, InvestigationNotificationService } from '../lib/email'; import { EmailNotificationEvent, EmailNotificationResponse } from '../lib/models/email-notification-service-event'; const environmentVariables = new EnvironmentVariablesService(); @@ -23,6 +23,7 @@ interface LambdaProperties { export class Lambda implements LambdaInterface { private readonly emailService: EmailNotificationService; private readonly encumbranceService: EncumbranceNotificationService; + private readonly investigationService: InvestigationNotificationService; constructor(props: LambdaProperties) { const compactConfigurationClient = new CompactConfigurationClient({ @@ -50,6 +51,14 @@ export class Lambda implements LambdaInterface { compactConfigurationClient: compactConfigurationClient, jurisdictionClient: jurisdictionClient }); + + this.investigationService = new InvestigationNotificationService({ + logger: logger, + sesClient: props.sesClient, + s3Client: props.s3Client, + compactConfigurationClient: compactConfigurationClient, + jurisdictionClient: jurisdictionClient + }); } /** @@ -63,7 +72,7 @@ export class Lambda implements LambdaInterface { * @returns Email notification response */ @logger.injectLambdaContext({ resetKeys: true }) - public async handler(event: EmailNotificationEvent, context: Context): Promise { + public async handler(event: EmailNotificationEvent, _context: Context): Promise { logger.info('Processing event', { template: event.template, compact: event.compact, jurisdiction: event.jurisdiction }); // Check if FROM_ADDRESS is configured @@ -358,6 +367,90 @@ export class Lambda implements LambdaInterface { event.templateVariables.recoveryToken ); break; + case 'licenseInvestigationStateNotification': + if (!event.jurisdiction) { + throw new Error('No jurisdiction provided for license investigation state notification email'); + } + if (!event.templateVariables?.providerFirstName + || !event.templateVariables?.providerLastName + || !event.templateVariables?.providerId + || !event.templateVariables?.investigationJurisdiction + || !event.templateVariables?.licenseType) { + throw new Error('Missing required template variables for licenseInvestigationStateNotification template.'); + } + await this.investigationService.sendLicenseInvestigationStateNotificationEmail( + event.compact, + event.jurisdiction, + event.templateVariables.providerFirstName, + event.templateVariables.providerLastName, + event.templateVariables.providerId, + event.templateVariables.investigationJurisdiction, + event.templateVariables.licenseType + ); + break; + case 'licenseInvestigationClosedStateNotification': + if (!event.jurisdiction) { + throw new Error('No jurisdiction provided for license investigation closed state notification email'); + } + if (!event.templateVariables?.providerFirstName + || !event.templateVariables?.providerLastName + || !event.templateVariables?.providerId + || !event.templateVariables?.investigationJurisdiction + || !event.templateVariables?.licenseType) { + throw new Error('Missing required template variables for licenseInvestigationClosedStateNotification template.'); + } + await this.investigationService.sendLicenseInvestigationClosedStateNotificationEmail( + event.compact, + event.jurisdiction, + event.templateVariables.providerFirstName, + event.templateVariables.providerLastName, + event.templateVariables.providerId, + event.templateVariables.investigationJurisdiction, + event.templateVariables.licenseType + ); + break; + case 'privilegeInvestigationStateNotification': + if (!event.jurisdiction) { + throw new Error('No jurisdiction provided for privilege investigation state notification email'); + } + if (!event.templateVariables?.providerFirstName + || !event.templateVariables?.providerLastName + || !event.templateVariables?.providerId + || !event.templateVariables?.investigationJurisdiction + || !event.templateVariables?.licenseType) { + throw new Error('Missing required template variables for privilegeInvestigationStateNotification template.'); + } + await this.investigationService.sendPrivilegeInvestigationStateNotificationEmail( + event.compact, + event.jurisdiction, + event.templateVariables.providerFirstName, + event.templateVariables.providerLastName, + event.templateVariables.providerId, + event.templateVariables.investigationJurisdiction, + event.templateVariables.licenseType + ); + break; + case 'privilegeInvestigationClosedStateNotification': + if (!event.jurisdiction) { + throw new Error('No jurisdiction provided for privilege investigation closed state notification email'); + } + if (!event.templateVariables?.providerFirstName + || !event.templateVariables?.providerLastName + || !event.templateVariables?.providerId + || !event.templateVariables?.investigationJurisdiction + || !event.templateVariables?.licenseType) { + throw new Error('Missing required template variables for privilegeInvestigationClosedStateNotification template.'); + } + await this.investigationService.sendPrivilegeInvestigationClosedStateNotificationEmail( + event.compact, + event.jurisdiction, + event.templateVariables.providerFirstName, + event.templateVariables.providerLastName, + event.templateVariables.providerId, + event.templateVariables.investigationJurisdiction, + event.templateVariables.licenseType + ); + break; default: logger.info('Unsupported email template provided', { template: event.template }); throw new Error(`Unsupported email template: ${event.template}`); diff --git a/backend/compact-connect/lambdas/nodejs/eslint.config.mjs b/backend/compact-connect/lambdas/nodejs/eslint.config.mjs index bea30f936..0961638b7 100644 --- a/backend/compact-connect/lambdas/nodejs/eslint.config.mjs +++ b/backend/compact-connect/lambdas/nodejs/eslint.config.mjs @@ -56,6 +56,14 @@ export default [ 'implicit-arrow-linebreak': OFF, 'class-methods-use-this': OFF, '@typescript-eslint/no-explicit-any': OFF, + 'no-unused-vars': OFF, // Disabled in favor of @typescript-eslint/no-unused-vars + '@typescript-eslint/no-unused-vars': [ERROR, { + vars: 'all', + args: 'after-used', + ignoreRestSiblings: true, + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + }], 'padding-line-between-statements': [ ERROR, { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, 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 3bc0b6e89..625c2e21f 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 @@ -92,7 +92,7 @@ export class EmailNotificationService extends BaseEmailService { if (errorDetails.unsettledTransactionIds && errorDetails.unsettledTransactionIds.length > 0) { bodyText += `Unsettled Transaction IDs (older than 48 hours): ${errorDetails.unsettledTransactionIds.join(', ')}\n`; } - } catch (parseError) { + } catch { // If JSON parsing fails, include the raw message bodyText += `\n\nError Details: ${batchFailureErrorMessage}`; } diff --git a/backend/compact-connect/lambdas/nodejs/lib/email/index.ts b/backend/compact-connect/lambdas/nodejs/lib/email/index.ts index 6a09b913a..66e5ba1e8 100644 --- a/backend/compact-connect/lambdas/nodejs/lib/email/index.ts +++ b/backend/compact-connect/lambdas/nodejs/lib/email/index.ts @@ -1,5 +1,6 @@ export { CognitoEmailService } from './cognito-email-service'; export { EmailNotificationService } from './email-notification-service'; export { EncumbranceNotificationService } from './encumbrance-notification-service'; +export { InvestigationNotificationService } from './investigation-notification-service'; export { IngestEventEmailService } from './ingest-event-email-service'; export { EnvironmentBannerService } from './environment-banner-service'; diff --git a/backend/compact-connect/lambdas/nodejs/lib/email/investigation-notification-service.ts b/backend/compact-connect/lambdas/nodejs/lib/email/investigation-notification-service.ts new file mode 100644 index 000000000..472624f3a --- /dev/null +++ b/backend/compact-connect/lambdas/nodejs/lib/email/investigation-notification-service.ts @@ -0,0 +1,287 @@ +import { BaseEmailService } from './base-email-service'; +import { IJurisdiction } from 'lib/models/jurisdiction'; + + +/** + * Service for handling investigation-related email notifications + */ +export class InvestigationNotificationService extends BaseEmailService { + private async getJurisdictionAdverseActionRecipients( + jurisdictionConfig: IJurisdiction + ): Promise { + const recipients = jurisdictionConfig.jurisdictionAdverseActionsNotificationEmails; + + if (recipients.length === 0) { + // If the state hasn't provided a contact for adverse actions, we note it and move on, preferring to + // continue with other notifications, rather than failing the entire notification process. + this.logger.warn('No adverse action notification recipients found for jurisdiction', { + compact: jurisdictionConfig.compact, + jurisdiction: jurisdictionConfig.postalAbbreviation + }); + return []; + } + + return recipients; + } + + /** + * Gets jurisdiction configurations and adverse action recipients for state notifications, + * handling errors gracefully by logging warnings and continuing + * @param compact - The compact name + * @param notifyingJurisdiction - The jurisdiction that should be notified + * @param affectedJurisdiction - The jurisdiction where the investigation occurred + * @param context - Context for logging (e.g., 'license investigation', 'privilege investigation closed') + * @returns Object containing recipients and affected jurisdiction config, or empty if error occurred + */ + private async getStateNotificationData( + compact: string, + notifyingJurisdiction: string, + affectedJurisdiction: string, + context: string + ): Promise<{ + recipients: string[]; + affectedJurisdictionConfig: IJurisdiction | undefined; + }> { + let affectedJurisdictionConfig: IJurisdiction | undefined; + let recipients: string[] = []; + + try { + const notifyingJurisdictionConfig = await this.jurisdictionClient.getJurisdictionConfiguration( + compact, notifyingJurisdiction + ); + + if (notifyingJurisdictionConfig.postalAbbreviation !== affectedJurisdiction) { + affectedJurisdictionConfig = await this.jurisdictionClient.getJurisdictionConfiguration( + compact, affectedJurisdiction + ); + } else { + affectedJurisdictionConfig = notifyingJurisdictionConfig; + } + + recipients = await this.getJurisdictionAdverseActionRecipients(notifyingJurisdictionConfig); + } catch (error) { + // If we have missing jurisdiction configuration, we note it and move on, preferring to + // continue, rather than failing the entire notification process. + this.logger.warn(`Error getting jurisdiction configuration for state ${context} notification email`, { + compact: compact, + notifyingJurisdiction: notifyingJurisdiction, + affectedJurisdiction: affectedJurisdiction, + error: error + }); + } + + return { recipients, affectedJurisdictionConfig }; + } + + /** + * Sends a license investigation notification email to state authorities + * @param compact - The compact name + * @param jurisdiction - The jurisdiction to notify + * @param providerFirstName - The provider's first name + * @param providerLastName - The provider's last name + * @param providerId - The provider's ID + * @param investigationJurisdiction - The jurisdiction where the license is under investigation + * @param licenseType - The license type that is under investigation + */ + public async sendLicenseInvestigationStateNotificationEmail( + compact: string, + jurisdiction: string, + providerFirstName: string, + providerLastName: string, + providerId: string, + investigationJurisdiction: string, + licenseType: string + ): Promise { + this.logger.info('Sending license investigation state notification email', { + compact: compact, + jurisdiction: jurisdiction + }); + + const { recipients, affectedJurisdictionConfig } = await this.getStateNotificationData( + compact, jurisdiction, investigationJurisdiction, 'license investigation' + ); + + if (recipients.length === 0) { + this.logger.warn('No recipients found for license investigation state notification', { + compact: compact, + jurisdiction: jurisdiction + }); + return; + } + + const compactConfig = await this.compactConfigurationClient.getCompactConfiguration(compact); + const report = this.getNewEmailTemplate(); + const subject = `${providerFirstName} ${providerLastName} holding ${licenseType} license in ${affectedJurisdictionConfig?.jurisdictionName} is under investigation`; + const bodyText = `This message is to notify you that ${providerFirstName} ${providerLastName} (Provider ID: ${providerId}) ` + + `holding a *${licenseType}* license in ${affectedJurisdictionConfig?.jurisdictionName} is under investigation ` + + `in the ${compactConfig.compactName} compact.\n\n` + + `Please contact the licensing board in ${affectedJurisdictionConfig?.jurisdictionName} for more information about this investigation.`; + + this.insertHeader(report, subject); + this.insertBody(report, bodyText, 'center', true); + this.insertFooter(report); + + const htmlContent = this.renderTemplate(report); + + await this.sendEmail({ htmlContent, subject, recipients, errorMessage: 'Unable to send license investigation state notification email' }); + } + + /** + * Sends a license investigation closed notification email to state authorities + * @param compact - The compact name + * @param jurisdiction - The jurisdiction to notify + * @param providerFirstName - The provider's first name + * @param providerLastName - The provider's last name + * @param providerId - The provider's ID + * @param investigationJurisdiction - The jurisdiction where the license investigation was closed + * @param licenseType - The license type that was under investigation + */ + public async sendLicenseInvestigationClosedStateNotificationEmail( + compact: string, + jurisdiction: string, + providerFirstName: string, + providerLastName: string, + providerId: string, + investigationJurisdiction: string, + licenseType: string + ): Promise { + this.logger.info('Sending license investigation closed state notification email', { + compact: compact, + jurisdiction: jurisdiction + }); + + const { recipients, affectedJurisdictionConfig } = await this.getStateNotificationData( + compact, jurisdiction, investigationJurisdiction, 'license investigation closed' + ); + + if (recipients.length === 0) { + this.logger.warn('No recipients found for license investigation closed state notification', { + compact: compact, + jurisdiction: jurisdiction + }); + return; + } + + const compactConfig = await this.compactConfigurationClient.getCompactConfiguration(compact); + const report = this.getNewEmailTemplate(); + const subject = `Investigation on ${providerFirstName} ${providerLastName}'s ${licenseType} license in ${affectedJurisdictionConfig?.jurisdictionName} has been closed`; + const bodyText = `This message is to notify you that the investigation on ${providerFirstName} ${providerLastName} (Provider ID: ${providerId}) ` + + `holding a *${licenseType}* license in ${affectedJurisdictionConfig?.jurisdictionName} has been closed ` + + `in the ${compactConfig.compactName} compact.\n\n` + + `Please contact the licensing board in ${affectedJurisdictionConfig?.jurisdictionName} for more information about this investigation closure.`; + + this.insertHeader(report, subject); + this.insertBody(report, bodyText, 'center', true); + this.insertFooter(report); + + const htmlContent = this.renderTemplate(report); + + await this.sendEmail({ htmlContent, subject, recipients, errorMessage: 'Unable to send license investigation closed state notification email' }); + } + + /** + * Sends a privilege investigation notification email to state authorities + * @param compact - The compact name + * @param jurisdiction - The jurisdiction to notify + * @param providerFirstName - The provider's first name + * @param providerLastName - The provider's last name + * @param providerId - The provider's ID + * @param investigationJurisdiction - The jurisdiction where the privilege is under investigation + * @param licenseType - The license type that is under investigation + */ + public async sendPrivilegeInvestigationStateNotificationEmail( + compact: string, + jurisdiction: string, + providerFirstName: string, + providerLastName: string, + providerId: string, + investigationJurisdiction: string, + licenseType: string + ): Promise { + this.logger.info('Sending privilege investigation state notification email', { + compact: compact, + jurisdiction: jurisdiction + }); + + const { recipients, affectedJurisdictionConfig } = await this.getStateNotificationData( + compact, jurisdiction, investigationJurisdiction, 'privilege investigation' + ); + + if (recipients.length === 0) { + this.logger.warn('No recipients found for privilege investigation state notification', { + compact: compact, + jurisdiction: jurisdiction + }); + return; + } + + const compactConfig = await this.compactConfigurationClient.getCompactConfiguration(compact); + const report = this.getNewEmailTemplate(); + const subject = `${providerFirstName} ${providerLastName} holding ${licenseType} privilege in ${affectedJurisdictionConfig?.jurisdictionName} is under investigation`; + const bodyText = `This message is to notify you that ${providerFirstName} ${providerLastName} (Provider ID: ${providerId}) ` + + `holding a *${licenseType}* privilege in ${affectedJurisdictionConfig?.jurisdictionName} is under investigation ` + + `in the ${compactConfig.compactName} compact.\n\n` + + `Please contact the licensing board in ${affectedJurisdictionConfig?.jurisdictionName} for more information about this investigation.`; + + this.insertHeader(report, subject); + this.insertBody(report, bodyText, 'center', true); + this.insertFooter(report); + + const htmlContent = this.renderTemplate(report); + + await this.sendEmail({ htmlContent, subject, recipients, errorMessage: 'Unable to send privilege investigation state notification email' }); + } + + /** + * Sends a privilege investigation closed notification email to state authorities + * @param compact - The compact name + * @param jurisdiction - The jurisdiction to notify + * @param providerFirstName - The provider's first name + * @param providerLastName - The provider's last name + * @param providerId - The provider's ID + * @param investigationJurisdiction - The jurisdiction where the privilege investigation was closed + * @param licenseType - The license type that was under investigation + */ + public async sendPrivilegeInvestigationClosedStateNotificationEmail( + compact: string, + jurisdiction: string, + providerFirstName: string, + providerLastName: string, + providerId: string, + investigationJurisdiction: string, + licenseType: string + ): Promise { + this.logger.info('Sending privilege investigation closed state notification email', { + compact: compact, + jurisdiction: jurisdiction + }); + + const { recipients, affectedJurisdictionConfig } = await this.getStateNotificationData( + compact, jurisdiction, investigationJurisdiction, 'privilege investigation closed' + ); + + if (recipients.length === 0) { + this.logger.warn('No recipients found for privilege investigation closed state notification', { + compact: compact, + jurisdiction: jurisdiction + }); + return; + } + + const compactConfig = await this.compactConfigurationClient.getCompactConfiguration(compact); + const report = this.getNewEmailTemplate(); + const subject = `Investigation on ${providerFirstName} ${providerLastName}'s ${licenseType} privilege in ${affectedJurisdictionConfig?.jurisdictionName} has been closed`; + const bodyText = `This message is to notify you that the investigation on ${providerFirstName} ${providerLastName} (Provider ID: ${providerId}) ` + + `holding a *${licenseType}* privilege in ${affectedJurisdictionConfig?.jurisdictionName} has been closed ` + + `in the ${compactConfig.compactName} compact.\n\n` + + `Please contact the licensing board in ${affectedJurisdictionConfig?.jurisdictionName} for more information about this investigation closure.`; + + this.insertHeader(report, subject); + this.insertBody(report, bodyText, 'center', true); + this.insertFooter(report); + + const htmlContent = this.renderTemplate(report); + + await this.sendEmail({ htmlContent, subject, recipients, errorMessage: 'Unable to send privilege investigation closed state 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 2243a14d8..83d3e7ab9 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 @@ -1502,4 +1502,368 @@ describe('EmailNotificationServiceLambda', () => { .toThrow('Missing required template variables for providerAccountRecoveryConfirmation template'); }); }); + + describe('License Investigation State Notification', () => { + const SAMPLE_LICENSE_INVESTIGATION_STATE_NOTIFICATION_EVENT: EmailNotificationEvent = { + template: 'licenseInvestigationStateNotification', + recipientType: 'JURISDICTION_ADVERSE_ACTIONS', + compact: 'aslp', + jurisdiction: 'ca', + templateVariables: { + providerFirstName: 'John', + providerLastName: 'Doe', + providerId: 'provider-123', + investigationJurisdiction: 'OH', + licenseType: 'Audiologist' + } + }; + + it('should successfully send license investigation state notification email', async () => { + const mockCaJurisdictionConfig = { + 'pk': { S: 'aslp#CONFIGURATION' }, + 'sk': { S: 'aslp#JURISDICTION#ca' }, + 'jurisdictionAdverseActionsNotificationEmails': { L: [{ S: 'ca-adverse@example.com' }]}, + 'type': { S: 'jurisdiction' } + }; + + const mockOhJurisdictionConfig = { + 'pk': { S: 'aslp#CONFIGURATION' }, + 'sk': { S: 'aslp#JURISDICTION#oh' }, + 'jurisdictionName': { S: 'Ohio' }, + 'type': { S: 'jurisdiction' } + }; + + mockDynamoDBClient.on(GetItemCommand).callsFake((input) => { + if (input.Key.sk.S === 'aslp#JURISDICTION#ca') { + return Promise.resolve({ Item: mockCaJurisdictionConfig }); + } else if (input.Key.sk.S === 'aslp#JURISDICTION#oh') { + return Promise.resolve({ Item: mockOhJurisdictionConfig }); + } + return Promise.resolve({ Item: SAMPLE_COMPACT_CONFIGURATION }); + }); + + const response = await lambda.handler(SAMPLE_LICENSE_INVESTIGATION_STATE_NOTIFICATION_EVENT, {} as any); + + expect(response).toEqual({ + message: 'Email message sent' + }); + + expect(mockSESClient).toHaveReceivedCommandWith(SendEmailCommand, { + Destination: { + ToAddresses: ['ca-adverse@example.com'] + }, + Content: { + Simple: { + Body: { + Html: { + Charset: 'UTF-8', + Data: expect.stringContaining('John Doe holding Audiologist license in Ohio is under investigation') + } + }, + Subject: { + Charset: 'UTF-8', + Data: 'John Doe holding Audiologist license in Ohio is under investigation' + } + } + }, + FromEmailAddress: 'Compact Connect ' + }); + }); + + it('should throw error when jurisdiction is missing', async () => { + const eventWithMissingJurisdiction: EmailNotificationEvent = { + ...SAMPLE_LICENSE_INVESTIGATION_STATE_NOTIFICATION_EVENT, + jurisdiction: undefined + }; + + await expect(lambda.handler(eventWithMissingJurisdiction, {} as any)) + .rejects + .toThrow('No jurisdiction provided for license investigation state notification email'); + }); + + it('should throw error when required template variables are missing', async () => { + const eventWithMissingVariables: EmailNotificationEvent = { + ...SAMPLE_LICENSE_INVESTIGATION_STATE_NOTIFICATION_EVENT, + templateVariables: {} + }; + + await expect(lambda.handler(eventWithMissingVariables, {} as any)) + .rejects + .toThrow('Missing required template variables for licenseInvestigationStateNotification template.'); + }); + }); + + describe('License Investigation Closed State Notification', () => { + const SAMPLE_LICENSE_INVESTIGATION_CLOSED_STATE_NOTIFICATION_EVENT: EmailNotificationEvent = { + template: 'licenseInvestigationClosedStateNotification', + recipientType: 'JURISDICTION_ADVERSE_ACTIONS', + compact: 'aslp', + jurisdiction: 'ca', + templateVariables: { + providerFirstName: 'John', + providerLastName: 'Doe', + providerId: 'provider-123', + investigationJurisdiction: 'OH', + licenseType: 'Audiologist' + } + }; + + it('should successfully send license investigation closed state notification email', async () => { + const mockCaJurisdictionConfig = { + 'pk': { S: 'aslp#CONFIGURATION' }, + 'sk': { S: 'aslp#JURISDICTION#ca' }, + 'jurisdictionAdverseActionsNotificationEmails': { L: [{ S: 'ca-adverse@example.com' }]}, + 'type': { S: 'jurisdiction' } + }; + + const mockOhJurisdictionConfig = { + 'pk': { S: 'aslp#CONFIGURATION' }, + 'sk': { S: 'aslp#JURISDICTION#oh' }, + 'jurisdictionName': { S: 'Ohio' }, + 'type': { S: 'jurisdiction' } + }; + + mockDynamoDBClient.on(GetItemCommand).callsFake((input) => { + if (input.Key.sk.S === 'aslp#JURISDICTION#ca') { + return Promise.resolve({ Item: mockCaJurisdictionConfig }); + } else if (input.Key.sk.S === 'aslp#JURISDICTION#oh') { + return Promise.resolve({ Item: mockOhJurisdictionConfig }); + } + return Promise.resolve({ Item: SAMPLE_COMPACT_CONFIGURATION }); + }); + + const response = await lambda.handler( + SAMPLE_LICENSE_INVESTIGATION_CLOSED_STATE_NOTIFICATION_EVENT, {} as any + ); + + expect(response).toEqual({ + message: 'Email message sent' + }); + + expect(mockSESClient).toHaveReceivedCommandWith(SendEmailCommand, { + Destination: { + ToAddresses: ['ca-adverse@example.com'] + }, + Content: { + Simple: { + Body: { + Html: { + Charset: 'UTF-8', + Data: expect.stringContaining('Investigation on John Doe') + } + }, + Subject: { + Charset: 'UTF-8', + Data: expect.stringMatching(/Investigation on John Doe.s Audiologist license in Ohio has been closed/) + } + } + }, + FromEmailAddress: 'Compact Connect ' + }); + }); + + it('should throw error when jurisdiction is missing', async () => { + const eventWithMissingJurisdiction: EmailNotificationEvent = { + ...SAMPLE_LICENSE_INVESTIGATION_CLOSED_STATE_NOTIFICATION_EVENT, + jurisdiction: undefined + }; + + await expect(lambda.handler(eventWithMissingJurisdiction, {} as any)) + .rejects + .toThrow('No jurisdiction provided for license investigation closed state notification email'); + }); + + it('should throw error when required template variables are missing', async () => { + const eventWithMissingVariables: EmailNotificationEvent = { + ...SAMPLE_LICENSE_INVESTIGATION_CLOSED_STATE_NOTIFICATION_EVENT, + templateVariables: {} + }; + + await expect(lambda.handler(eventWithMissingVariables, {} as any)) + .rejects + .toThrow('Missing required template variables for licenseInvestigationClosedStateNotification template.'); + }); + }); + + describe('Privilege Investigation State Notification', () => { + const SAMPLE_PRIVILEGE_INVESTIGATION_STATE_NOTIFICATION_EVENT: EmailNotificationEvent = { + template: 'privilegeInvestigationStateNotification', + recipientType: 'JURISDICTION_ADVERSE_ACTIONS', + compact: 'aslp', + jurisdiction: 'ca', + templateVariables: { + providerFirstName: 'John', + providerLastName: 'Doe', + providerId: 'provider-123', + investigationJurisdiction: 'OH', + licenseType: 'Audiologist' + } + }; + + it('should successfully send privilege investigation state notification email', async () => { + const mockCaJurisdictionConfig = { + 'pk': { S: 'aslp#CONFIGURATION' }, + 'sk': { S: 'aslp#JURISDICTION#ca' }, + 'jurisdictionAdverseActionsNotificationEmails': { L: [{ S: 'ca-adverse@example.com' }]}, + 'type': { S: 'jurisdiction' } + }; + + const mockOhJurisdictionConfig = { + 'pk': { S: 'aslp#CONFIGURATION' }, + 'sk': { S: 'aslp#JURISDICTION#oh' }, + 'jurisdictionName': { S: 'Ohio' }, + 'type': { S: 'jurisdiction' } + }; + + mockDynamoDBClient.on(GetItemCommand).callsFake((input) => { + if (input.Key.sk.S === 'aslp#JURISDICTION#ca') { + return Promise.resolve({ Item: mockCaJurisdictionConfig }); + } else if (input.Key.sk.S === 'aslp#JURISDICTION#oh') { + return Promise.resolve({ Item: mockOhJurisdictionConfig }); + } + return Promise.resolve({ Item: SAMPLE_COMPACT_CONFIGURATION }); + }); + + const response = await lambda.handler(SAMPLE_PRIVILEGE_INVESTIGATION_STATE_NOTIFICATION_EVENT, {} as any); + + expect(response).toEqual({ + message: 'Email message sent' + }); + + expect(mockSESClient).toHaveReceivedCommandWith(SendEmailCommand, { + Destination: { + ToAddresses: ['ca-adverse@example.com'] + }, + Content: { + Simple: { + Body: { + Html: { + Charset: 'UTF-8', + Data: expect.stringContaining('John Doe holding Audiologist privilege in Ohio is under investigation') + } + }, + Subject: { + Charset: 'UTF-8', + Data: 'John Doe holding Audiologist privilege in Ohio is under investigation' + } + } + }, + FromEmailAddress: 'Compact Connect ' + }); + }); + + it('should throw error when jurisdiction is missing', async () => { + const eventWithMissingJurisdiction: EmailNotificationEvent = { + ...SAMPLE_PRIVILEGE_INVESTIGATION_STATE_NOTIFICATION_EVENT, + jurisdiction: undefined + }; + + await expect(lambda.handler(eventWithMissingJurisdiction, {} as any)) + .rejects + .toThrow('No jurisdiction provided for privilege investigation state notification email'); + }); + + it('should throw error when required template variables are missing', async () => { + const eventWithMissingVariables: EmailNotificationEvent = { + ...SAMPLE_PRIVILEGE_INVESTIGATION_STATE_NOTIFICATION_EVENT, + templateVariables: {} + }; + + await expect(lambda.handler(eventWithMissingVariables, {} as any)) + .rejects + .toThrow('Missing required template variables for privilegeInvestigationStateNotification template.'); + }); + }); + + describe('Privilege Investigation Closed State Notification', () => { + const SAMPLE_PRIVILEGE_INVESTIGATION_CLOSED_STATE_NOTIFICATION_EVENT: EmailNotificationEvent = { + template: 'privilegeInvestigationClosedStateNotification', + recipientType: 'JURISDICTION_ADVERSE_ACTIONS', + compact: 'aslp', + jurisdiction: 'ca', + templateVariables: { + providerFirstName: 'John', + providerLastName: 'Doe', + providerId: 'provider-123', + investigationJurisdiction: 'OH', + licenseType: 'Audiologist' + } + }; + + it('should successfully send privilege investigation closed state notification email', async () => { + const mockCaJurisdictionConfig = { + 'pk': { S: 'aslp#CONFIGURATION' }, + 'sk': { S: 'aslp#JURISDICTION#ca' }, + 'jurisdictionAdverseActionsNotificationEmails': { L: [{ S: 'ca-adverse@example.com' }]}, + 'type': { S: 'jurisdiction' } + }; + + const mockOhJurisdictionConfig = { + 'pk': { S: 'aslp#CONFIGURATION' }, + 'sk': { S: 'aslp#JURISDICTION#oh' }, + 'jurisdictionName': { S: 'Ohio' }, + 'type': { S: 'jurisdiction' } + }; + + mockDynamoDBClient.on(GetItemCommand).callsFake((input) => { + if (input.Key.sk.S === 'aslp#JURISDICTION#ca') { + return Promise.resolve({ Item: mockCaJurisdictionConfig }); + } else if (input.Key.sk.S === 'aslp#JURISDICTION#oh') { + return Promise.resolve({ Item: mockOhJurisdictionConfig }); + } + return Promise.resolve({ Item: SAMPLE_COMPACT_CONFIGURATION }); + }); + + const response = await lambda.handler( + SAMPLE_PRIVILEGE_INVESTIGATION_CLOSED_STATE_NOTIFICATION_EVENT, {} as any + ); + + expect(response).toEqual({ + message: 'Email message sent' + }); + + expect(mockSESClient).toHaveReceivedCommandWith(SendEmailCommand, { + Destination: { + ToAddresses: ['ca-adverse@example.com'] + }, + Content: { + Simple: { + Body: { + Html: { + Charset: 'UTF-8', + Data: expect.stringContaining('Investigation on John Doe') + } + }, + Subject: { + Charset: 'UTF-8', + Data: expect.stringMatching(/Investigation on John Doe.s Audiologist privilege in Ohio has been closed/) + } + } + }, + FromEmailAddress: 'Compact Connect ' + }); + }); + + it('should throw error when jurisdiction is missing', async () => { + const eventWithMissingJurisdiction: EmailNotificationEvent = { + ...SAMPLE_PRIVILEGE_INVESTIGATION_CLOSED_STATE_NOTIFICATION_EVENT, + jurisdiction: undefined + }; + + await expect(lambda.handler(eventWithMissingJurisdiction, {} as any)) + .rejects + .toThrow('No jurisdiction provided for privilege investigation closed state notification email'); + }); + + it('should throw error when required template variables are missing', async () => { + const eventWithMissingVariables: EmailNotificationEvent = { + ...SAMPLE_PRIVILEGE_INVESTIGATION_CLOSED_STATE_NOTIFICATION_EVENT, + templateVariables: {} + }; + + await expect(lambda.handler(eventWithMissingVariables, {} as any)) + .rejects + .toThrow('Missing required template variables for privilegeInvestigationClosedStateNotification template.'); + }); + }); }); diff --git a/backend/compact-connect/lambdas/nodejs/tests/ingest-event-reporter.test.ts b/backend/compact-connect/lambdas/nodejs/tests/ingest-event-reporter.test.ts index 34dfd9303..194999d08 100644 --- a/backend/compact-connect/lambdas/nodejs/tests/ingest-event-reporter.test.ts +++ b/backend/compact-connect/lambdas/nodejs/tests/ingest-event-reporter.test.ts @@ -1,12 +1,11 @@ import { mockClient } from 'aws-sdk-client-mock'; import 'aws-sdk-client-mock-jest'; -import { Context, EventBridgeEvent } from 'aws-lambda'; +import { Context } from 'aws-lambda'; import { DynamoDBClient, QueryCommand, GetItemCommand } from '@aws-sdk/client-dynamodb'; import { SendEmailCommand, SESv2Client } from '@aws-sdk/client-sesv2'; import { S3Client } from '@aws-sdk/client-s3'; import { Lambda } from '../ingest-event-reporter/lambda'; -import { IngestEventEmailService } from '../lib/email'; import { IEventBridgeEvent } from '../lib/models/event-bridge-event-detail'; import { SAMPLE_INGEST_FAILURE_ERROR_RECORD, @@ -64,13 +63,13 @@ jest.mock('../lib/email/ingest-event-email-service', () => { }); const mockSendReportEmail = jest.fn().mockImplementation( - (events, recipients: string[]) => Promise.resolve('message-id-123') + (_events, _recipients: string[]) => Promise.resolve('message-id-123') ); const mockSendAllsWellEmail = jest.fn().mockImplementation( - (recipients: string[]) => Promise.resolve('message-id-123') + (_recipients: string[]) => Promise.resolve('message-id-123') ); const mockSendNoLicenseUpdatesEmail = jest.fn().mockImplementation( - (recipients: string[]) => Promise.resolve('message-id-no-license-updates') + (_recipients: string[]) => Promise.resolve('message-id-no-license-updates') ); describe('Frequent runs', () => { @@ -146,7 +145,7 @@ describe('Frequent runs', () => { sesClient: asSESClient(mockSESClient) }); - const resp = await lambda.handler( + await lambda.handler( SAMPLE_NIGHTLY_EVENT, SAMPLE_CONTEXT ); @@ -213,7 +212,7 @@ describe('Frequent runs', () => { sesClient: asSESClient(mockSESClient) }); - const resp = await lambda.handler( + await lambda.handler( SAMPLE_NIGHTLY_EVENT, SAMPLE_CONTEXT ); @@ -510,7 +509,7 @@ describe('Weekly runs', () => { sesClient: asSESClient(mockSESClient) }); - const resp = await lambda.handler( + await lambda.handler( SAMPLE_WEEKLY_EVENT, SAMPLE_CONTEXT ); diff --git a/backend/compact-connect/lambdas/nodejs/tests/lib/email/email-notification-service.test.ts b/backend/compact-connect/lambdas/nodejs/tests/lib/email/email-notification-service.test.ts index 94f44d418..2befda98e 100644 --- a/backend/compact-connect/lambdas/nodejs/tests/lib/email/email-notification-service.test.ts +++ b/backend/compact-connect/lambdas/nodejs/tests/lib/email/email-notification-service.test.ts @@ -48,10 +48,6 @@ const asSESClient = (mock: ReturnType) => const asS3Client = (mock: ReturnType) => mock as unknown as S3Client; -interface MockMailResponse { - messageId: string; -} - const MOCK_TRANSPORT = { sendMail: jest.fn().mockImplementation(async () => ({ messageId: 'test-message-id' })) }; diff --git a/backend/compact-connect/lambdas/nodejs/tests/lib/email/encumbrance-notification-service.test.ts b/backend/compact-connect/lambdas/nodejs/tests/lib/email/encumbrance-notification-service.test.ts index 33bb23748..fd7271eea 100644 --- a/backend/compact-connect/lambdas/nodejs/tests/lib/email/encumbrance-notification-service.test.ts +++ b/backend/compact-connect/lambdas/nodejs/tests/lib/email/encumbrance-notification-service.test.ts @@ -53,7 +53,7 @@ class MockCompactConfigurationClient extends CompactConfigurationClient { }); } - public async getCompactConfiguration(compact: string): Promise { + public async getCompactConfiguration(_compact: string): Promise { return SAMPLE_COMPACT_CONFIG; } } diff --git a/backend/compact-connect/lambdas/nodejs/tests/lib/email/investigation-notification-service.test.ts b/backend/compact-connect/lambdas/nodejs/tests/lib/email/investigation-notification-service.test.ts new file mode 100644 index 000000000..f28ef7d00 --- /dev/null +++ b/backend/compact-connect/lambdas/nodejs/tests/lib/email/investigation-notification-service.test.ts @@ -0,0 +1,411 @@ +import { mockClient } from 'aws-sdk-client-mock'; +import 'aws-sdk-client-mock-jest'; +import { Logger } from '@aws-lambda-powertools/logger'; +import { SendEmailCommand, SESv2Client } from '@aws-sdk/client-sesv2'; +import { S3Client } from '@aws-sdk/client-s3'; +import { InvestigationNotificationService } from '../../../lib/email'; +import { CompactConfigurationClient } from '../../../lib/compact-configuration-client'; +import { JurisdictionClient } from '../../../lib/jurisdiction-client'; +import { EmailTemplateCapture } from '../../utils/email-template-capture'; +import { TReaderDocument } from '@jusdino-ia/email-builder'; +import { describe, it, expect, beforeEach, beforeAll, afterAll, jest } from '@jest/globals'; +import { Compact } from '../../../lib/models/compact'; +import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; + +const SAMPLE_COMPACT_CONFIG: Compact = { + pk: 'aslp#CONFIGURATION', + sk: 'aslp#CONFIGURATION', + compactAdverseActionsNotificationEmails: ['adverse@example.com'], + compactCommissionFee: { + feeAmount: 3.5, + feeType: 'FLAT_RATE' + }, + compactAbbr: 'aslp', + compactName: 'Audiology and Speech Language Pathology', + compactOperationsTeamEmails: ['operations@example.com'], + compactSummaryReportNotificationEmails: ['summary@example.com'], + dateOfUpdate: '2024-12-10T19:27:28+00:00', + type: 'compact' +}; + +const SAMPLE_JURISDICTION_CONFIG = { + pk: 'aslp#CONFIGURATION', + sk: 'aslp#JURISDICTION#oh', + jurisdictionName: 'Ohio', + postalAbbreviation: 'oh', + compact: 'aslp', + jurisdictionOperationsTeamEmails: ['oh-ops@example.com'], + jurisdictionAdverseActionsNotificationEmails: ['oh-adverse@example.com'], + jurisdictionSummaryReportNotificationEmails: ['oh-summary@example.com'] +}; + +const asSESClient = (mock: ReturnType) => + mock as unknown as SESv2Client; + +const asS3Client = (mock: ReturnType) => + mock as unknown as S3Client; + +class MockCompactConfigurationClient extends CompactConfigurationClient { + constructor() { + super({ + logger: new Logger({ serviceName: 'test' }), + dynamoDBClient: {} as DynamoDBClient + }); + } + + public async getCompactConfiguration(_compact: string): Promise { + return SAMPLE_COMPACT_CONFIG; + } +} + +describe('InvestigationNotificationService', () => { + let investigationService: InvestigationNotificationService; + let mockSESClient: ReturnType; + let mockS3Client: ReturnType; + let mockCompactConfigurationClient: MockCompactConfigurationClient; + let mockJurisdictionClient: jest.Mocked; + + beforeAll(() => { + // Mock the renderTemplate method if template capture is enabled + if (EmailTemplateCapture.isEnabled()) { + const original = (InvestigationNotificationService.prototype as any).renderTemplate; + + jest.spyOn(InvestigationNotificationService.prototype as any, 'renderTemplate').mockImplementation(function (this: any, ...args: any[]) { + const [template, options] = args as [TReaderDocument, any]; + + EmailTemplateCapture.captureTemplate(template); + const html = original.apply(this, args); + + EmailTemplateCapture.captureHtml(html, template, options); + return html; + }); + } + }); + + afterAll(() => { + jest.restoreAllMocks(); + }); + + beforeEach(() => { + jest.clearAllMocks(); + mockSESClient = mockClient(SESv2Client); + mockS3Client = mockClient(S3Client); + mockCompactConfigurationClient = new MockCompactConfigurationClient(); + mockJurisdictionClient = { + getJurisdictionConfigurations: jest.fn(), + getJurisdictionConfiguration: jest.fn() + } as any; + + // Reset environment variables + process.env.FROM_ADDRESS = 'noreply@example.org'; + process.env.UI_BASE_PATH_URL = 'https://app.test.compactconnect.org'; + + // Set up default successful responses + mockSESClient.on(SendEmailCommand).resolves({ + MessageId: 'message-id-123' + }); + + mockJurisdictionClient.getJurisdictionConfiguration.mockResolvedValue(SAMPLE_JURISDICTION_CONFIG); + + investigationService = new InvestigationNotificationService({ + logger: new Logger({ serviceName: 'test' }), + sesClient: asSESClient(mockSESClient), + s3Client: asS3Client(mockS3Client), + compactConfigurationClient: mockCompactConfigurationClient, + jurisdictionClient: mockJurisdictionClient + }); + }); + + describe('License Investigation State Notification', () => { + it('should send license investigation state notification email', async () => { + await investigationService.sendLicenseInvestigationStateNotificationEmail( + 'aslp', + 'OH', + 'John', + 'Doe', + 'provider-123', + 'OH', + 'Audiologist' + ); + + expect(mockSESClient).toHaveReceivedCommandWith(SendEmailCommand, { + Destination: { + ToAddresses: ['oh-adverse@example.com'] + }, + Content: { + Simple: { + Body: { + Html: { + Charset: 'UTF-8', + Data: expect.stringContaining('John Doe holding Audiologist license in Ohio is under investigation') + } + }, + Subject: { + Charset: 'UTF-8', + Data: 'John Doe holding Audiologist license in Ohio is under investigation' + } + } + }, + FromEmailAddress: 'Compact Connect ' + }); + }); + + it('should handle missing jurisdiction configuration gracefully', async () => { + mockJurisdictionClient.getJurisdictionConfiguration.mockRejectedValue(new Error('Jurisdiction not found')); + + await investigationService.sendLicenseInvestigationStateNotificationEmail( + 'aslp', + 'OH', + 'John', + 'Doe', + 'provider-123', + 'OH', + 'Audiologist' + ); + + // Should not throw an error, but also should not send email + expect(mockSESClient).not.toHaveReceivedCommand(SendEmailCommand); + }); + }); + + describe('License Investigation Closed State Notification', () => { + it('should send license investigation closed state notification email', async () => { + await investigationService.sendLicenseInvestigationClosedStateNotificationEmail( + 'aslp', + 'OH', + 'John', + 'Doe', + 'provider-123', + 'OH', + 'Audiologist' + ); + + expect(mockSESClient).toHaveReceivedCommandWith(SendEmailCommand, { + Destination: { + ToAddresses: ['oh-adverse@example.com'] + }, + Content: { + Simple: { + Body: { + Html: { + Charset: 'UTF-8', + Data: expect.stringContaining('holding a Audiologist license in Ohio has been closed') + } + }, + Subject: { + Charset: 'UTF-8', + Data: 'Investigation on John Doe\'s Audiologist license in Ohio has been closed' + } + } + }, + FromEmailAddress: 'Compact Connect ' + }); + }); + }); + + describe('Privilege Investigation State Notification', () => { + it('should send privilege investigation state notification email', async () => { + await investigationService.sendPrivilegeInvestigationStateNotificationEmail( + 'aslp', + 'OH', + 'John', + 'Doe', + 'provider-123', + 'OH', + 'Audiologist' + ); + + expect(mockSESClient).toHaveReceivedCommandWith(SendEmailCommand, { + Destination: { + ToAddresses: ['oh-adverse@example.com'] + }, + Content: { + Simple: { + Body: { + Html: { + Charset: 'UTF-8', + Data: expect.stringContaining('John Doe holding Audiologist privilege in Ohio is under investigation') + } + }, + Subject: { + Charset: 'UTF-8', + Data: 'John Doe holding Audiologist privilege in Ohio is under investigation' + } + } + }, + FromEmailAddress: 'Compact Connect ' + }); + }); + }); + + describe('Privilege Investigation Closed State Notification', () => { + it('should send privilege investigation closed state notification email', async () => { + await investigationService.sendPrivilegeInvestigationClosedStateNotificationEmail( + 'aslp', + 'OH', + 'John', + 'Doe', + 'provider-123', + 'OH', + 'Audiologist' + ); + + expect(mockSESClient).toHaveReceivedCommandWith(SendEmailCommand, { + Destination: { + ToAddresses: ['oh-adverse@example.com'] + }, + Content: { + Simple: { + Body: { + Html: { + Charset: 'UTF-8', + Data: expect.stringContaining('holding a Audiologist privilege in Ohio has been closed') + } + }, + Subject: { + Charset: 'UTF-8', + Data: 'Investigation on John Doe\'s Audiologist privilege in Ohio has been closed' + } + } + }, + FromEmailAddress: 'Compact Connect ' + }); + }); + }); + + describe('Error Handling', () => { + it('should handle SES client errors gracefully', async () => { + mockSESClient.on(SendEmailCommand).rejects(new Error('SES service error')); + + await expect( + investigationService.sendLicenseInvestigationStateNotificationEmail( + 'aslp', + 'OH', + 'John', + 'Doe', + 'provider-123', + 'OH', + 'Audiologist' + ) + ).rejects.toThrow('SES service error'); + }); + + it('should handle missing adverse action recipients gracefully', async () => { + const jurisdictionConfigWithoutAdverseActions = { + ...SAMPLE_JURISDICTION_CONFIG, + jurisdictionAdverseActionsNotificationEmails: [] + }; + + mockJurisdictionClient.getJurisdictionConfiguration.mockResolvedValue( + jurisdictionConfigWithoutAdverseActions + ); + + await investigationService.sendLicenseInvestigationStateNotificationEmail( + 'aslp', + 'OH', + 'John', + 'Doe', + 'provider-123', + 'OH', + 'Audiologist' + ); + + // Should not throw an error, but also should not send email + expect(mockSESClient).not.toHaveReceivedCommand(SendEmailCommand); + }); + + it('should handle missing recipients for license investigation closed state notification', async () => { + const jurisdictionConfigWithoutAdverseActions = { + ...SAMPLE_JURISDICTION_CONFIG, + jurisdictionAdverseActionsNotificationEmails: [] + }; + + mockJurisdictionClient.getJurisdictionConfiguration.mockResolvedValue( + jurisdictionConfigWithoutAdverseActions + ); + + await investigationService.sendLicenseInvestigationClosedStateNotificationEmail( + 'aslp', + 'OH', + 'John', + 'Doe', + 'provider-123', + 'OH', + 'Audiologist' + ); + + // Should not throw an error, but also should not send email + expect(mockSESClient).not.toHaveReceivedCommand(SendEmailCommand); + }); + + it('should handle missing recipients for privilege investigation state notification', async () => { + const jurisdictionConfigWithoutAdverseActions = { + ...SAMPLE_JURISDICTION_CONFIG, + jurisdictionAdverseActionsNotificationEmails: [] + }; + + mockJurisdictionClient.getJurisdictionConfiguration.mockResolvedValue( + jurisdictionConfigWithoutAdverseActions + ); + + await investigationService.sendPrivilegeInvestigationStateNotificationEmail( + 'aslp', + 'OH', + 'John', + 'Doe', + 'provider-123', + 'OH', + 'Audiologist' + ); + + // Should not throw an error, but also should not send email + expect(mockSESClient).not.toHaveReceivedCommand(SendEmailCommand); + }); + + it('should handle missing recipients for privilege investigation closed state notification', async () => { + const jurisdictionConfigWithoutAdverseActions = { + ...SAMPLE_JURISDICTION_CONFIG, + jurisdictionAdverseActionsNotificationEmails: [] + }; + + mockJurisdictionClient.getJurisdictionConfiguration.mockResolvedValue( + jurisdictionConfigWithoutAdverseActions + ); + + await investigationService.sendPrivilegeInvestigationClosedStateNotificationEmail( + 'aslp', + 'OH', + 'John', + 'Doe', + 'provider-123', + 'OH', + 'Audiologist' + ); + + // Should not throw an error, but also should not send email + expect(mockSESClient).not.toHaveReceivedCommand(SendEmailCommand); + }); + + it('should handle same notifying and affected jurisdiction', async () => { + // Reset mocks to ensure clean state + jest.clearAllMocks(); + mockJurisdictionClient.getJurisdictionConfiguration.mockResolvedValue(SAMPLE_JURISDICTION_CONFIG); + + // When notifying jurisdiction equals affected jurisdiction, it should use the same config + await investigationService.sendLicenseInvestigationStateNotificationEmail( + 'aslp', + 'OH', + 'John', + 'Doe', + 'provider-123', + 'OH', // Same as notifying jurisdiction + 'Audiologist' + ); + + // Should have been called at least once with the jurisdiction + expect(mockJurisdictionClient.getJurisdictionConfiguration).toHaveBeenCalledWith('aslp', 'OH'); + // Should have sent the email successfully + expect(mockSESClient).toHaveReceivedCommand(SendEmailCommand); + }); + }); +}); diff --git a/backend/compact-connect/lambdas/python/cognito-backup/requirements-dev.in b/backend/compact-connect/lambdas/python/cognito-backup/requirements-dev.in index 514e004e9..39256a5a9 100644 --- a/backend/compact-connect/lambdas/python/cognito-backup/requirements-dev.in +++ b/backend/compact-connect/lambdas/python/cognito-backup/requirements-dev.in @@ -2,4 +2,4 @@ boto3>=1.26.0 botocore>=1.29.0 aws-lambda-powertools>=2.0.0 pytest -moto[cognito-idp,s3] +moto[cognitoidp,s3]>=5, <6 diff --git a/backend/compact-connect/lambdas/python/cognito-backup/requirements-dev.txt b/backend/compact-connect/lambdas/python/cognito-backup/requirements-dev.txt index b9e90ad65..ccf9009a7 100644 --- a/backend/compact-connect/lambdas/python/cognito-backup/requirements-dev.txt +++ b/backend/compact-connect/lambdas/python/cognito-backup/requirements-dev.txt @@ -1,32 +1,34 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/cognito-backup/requirements-dev.in +# pip-compile --no-emit-index-url --no-strip-extras lambdas/python/cognito-backup/requirements-dev.in # -aws-lambda-powertools==3.20.0 +aws-lambda-powertools==3.22.0 # via -r lambdas/python/cognito-backup/requirements-dev.in -boto3==1.40.35 +boto3==1.40.56 # via # -r lambdas/python/cognito-backup/requirements-dev.in # moto -botocore==1.40.35 +botocore==1.40.56 # via # -r lambdas/python/cognito-backup/requirements-dev.in # boto3 # moto # s3transfer -certifi==2025.8.3 +certifi==2025.10.5 # via requests cffi==2.0.0 # via cryptography -charset-normalizer==3.4.3 +charset-normalizer==3.4.4 # via requests -cryptography==46.0.1 - # via moto -idna==3.10 +cryptography==46.0.3 + # via + # joserfc + # moto +idna==3.11 # via requests -iniconfig==2.1.0 +iniconfig==2.3.0 # via pytest jinja2==3.1.6 # via moto @@ -35,11 +37,13 @@ jmespath==1.0.1 # aws-lambda-powertools # boto3 # botocore -markupsafe==3.0.2 +joserfc==1.4.0 + # via moto +markupsafe==3.0.3 # via # jinja2 # werkzeug -moto[cognito-idp,s3]==5.1.12 +moto[cognitoidp,s3]==5.1.15 # via -r lambdas/python/cognito-backup/requirements-dev.in packaging==25.0 # via pytest @@ -57,7 +61,7 @@ python-dateutil==2.9.0.post0 # via # botocore # moto -pyyaml==6.0.2 +pyyaml==6.0.3 # via # moto # responses diff --git a/backend/compact-connect/lambdas/python/cognito-backup/requirements.txt b/backend/compact-connect/lambdas/python/cognito-backup/requirements.txt index 8d02a8f2d..a9ee3d599 100644 --- a/backend/compact-connect/lambdas/python/cognito-backup/requirements.txt +++ b/backend/compact-connect/lambdas/python/cognito-backup/requirements.txt @@ -1,14 +1,14 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/cognito-backup/requirements.in +# pip-compile --no-emit-index-url --no-strip-extras lambdas/python/cognito-backup/requirements.in # -aws-lambda-powertools==3.20.0 +aws-lambda-powertools==3.22.0 # via -r lambdas/python/cognito-backup/requirements.in -boto3==1.40.35 +boto3==1.40.56 # via -r lambdas/python/cognito-backup/requirements.in -botocore==1.40.35 +botocore==1.40.56 # via # -r lambdas/python/cognito-backup/requirements.in # boto3 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 1156b72ab..4cb052a8e 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 @@ -2,7 +2,7 @@ from datetime import date, datetime from datetime import time as dtime from urllib.parse import quote -from uuid import uuid4 +from uuid import UUID, uuid4 from aws_lambda_powertools.metrics import MetricUnit from boto3.dynamodb.conditions import Attr, Key @@ -23,11 +23,14 @@ CCDataClass, CompactEligibilityStatus, HomeJurisdictionChangeStatusEnum, + InvestigationAgainstEnum, + InvestigationStatusEnum, LicenseDeactivatedStatusEnum, LicenseEncumberedStatusEnum, PrivilegeEncumberedStatusEnum, UpdateCategory, ) +from cc_common.data_model.schema.investigation import InvestigationData from cc_common.data_model.schema.license import LicenseData, LicenseUpdateData from cc_common.data_model.schema.military_affiliation import MilitaryAffiliationData from cc_common.data_model.schema.military_affiliation.common import ( @@ -174,7 +177,7 @@ def get_provider_user_records( self, *, compact: str, - provider_id: str, + provider_id: UUID, consistent_read: bool = True, ) -> ProviderUserRecords: logger.info('Getting provider') @@ -338,10 +341,6 @@ def _generate_privilege_record( if original_privilege: # Copy over the original issuance date and privilege id date_of_issuance = original_privilege.dateOfIssuance - # TODO: This privilege number copy-over approach has a gap in it, in the event that a # noqa: FIX002 - # provider's license type changes. In that event, the privilege id will have the original - # license type abbreviation in it, not the new one. - # This gap should be closed as part of https://github.com/csg-org/CompactConnect/issues/443. privilege_id = original_privilege.privilegeId else: date_of_issuance = current_datetime @@ -594,7 +593,7 @@ def _rollback_privilege_transactions( } } ) - elif item.get('type') == 'privilege': + elif item.get('type') == ProviderRecordType.PRIVILEGE: # For privilege records, check if it was an update or new creation original_privilege = existing_privileges_by_jurisdiction.get(item['jurisdiction']) if original_privilege: @@ -735,7 +734,7 @@ def create_military_affiliation( logger.info('Creating military affiliation') latest_military_affiliation_record = { - 'type': 'militaryAffiliation', + 'type': ProviderRecordType.MILITARY_AFFILIATION, 'affiliationType': affiliation_type.value, 'fileNames': file_names, 'compact': compact, @@ -915,7 +914,7 @@ def process_registration_values( # Create provider update record to show registration event and fields that were updated provider_update_record = ProviderUpdateData.create_new( { - 'type': 'providerUpdate', + 'type': ProviderRecordType.PROVIDER_UPDATE, 'updateType': UpdateCategory.REGISTRATION, 'providerId': matched_license_record.providerId, 'compact': matched_license_record.compact, @@ -1052,7 +1051,7 @@ def deactivate_privilege( # Use the schema to generate the update record with proper pk/sk privilege_update_record = PrivilegeUpdateRecordSchema().dump( { - 'type': 'privilegeUpdate', + 'type': ProviderRecordType.PRIVILEGE_UPDATE, 'updateType': 'deactivation', 'providerId': provider_id, 'compact': compact, @@ -1224,13 +1223,13 @@ def _validate_license_type_abbreviation(self, compact: str, license_type_abbrevi return LicenseUtility.get_license_type_by_abbreviation(compact, license_type_abbreviation).name def _find_and_validate_adverse_action( - self, adverse_action_records: list[AdverseActionData], adverse_action_id: str + self, adverse_action_records: list[AdverseActionData], adverse_action_id: UUID ) -> AdverseActionData: """ Find and validate an adverse action record from a list of records. :param list[AdverseActionData] adverse_action_records: List of adverse action records to search - :param str adverse_action_id: The ID of the adverse action to find + :param UUID adverse_action_id: The ID of the adverse action to find :return: The found adverse action record :raises CCNotFoundException: If the adverse action record is not found :raises CCInvalidRequestException: If the encumbrance has already been lifted @@ -1238,7 +1237,7 @@ def _find_and_validate_adverse_action( # Find the specific adverse action record to lift target_adverse_action: AdverseActionData | None = None for adverse_action in adverse_action_records: - if str(adverse_action.adverseActionId) == adverse_action_id: + if adverse_action.adverseActionId == adverse_action_id: target_adverse_action = adverse_action break @@ -1252,19 +1251,19 @@ def _find_and_validate_adverse_action( return target_adverse_action def _get_unlifted_adverse_actions( - self, adverse_action_records: list[AdverseActionData], target_adverse_action_id: str + self, adverse_action_records: list[AdverseActionData], target_adverse_action_id: UUID ) -> list[AdverseActionData]: """ Get all unlifted adverse actions excluding the target adverse action. :param list[AdverseActionData] adverse_action_records: List of adverse action records - :param str target_adverse_action_id: The ID of the target adverse action being lifted + :param UUID target_adverse_action_id: The ID of the target adverse action being lifted :return: List of unlifted adverse actions excluding the target one """ return [ aa for aa in adverse_action_records - if aa.effectiveLiftDate is None and str(aa.adverseActionId) != target_adverse_action_id + if aa.effectiveLiftDate is None and aa.adverseActionId != target_adverse_action_id ] def _generate_provider_encumbered_status_update_item_if_not_already_encumbered( @@ -1452,7 +1451,7 @@ def encumber_privilege(self, adverse_action: AdverseActionData) -> None: # Use the schema to generate the update record with proper pk/sk privilege_update_record = PrivilegeUpdateData.create_new( { - 'type': 'privilegeUpdate', + 'type': ProviderRecordType.PRIVILEGE_UPDATE, 'updateType': UpdateCategory.ENCUMBRANCE, 'providerId': adverse_action.providerId, 'compact': adverse_action.compact, @@ -1610,13 +1609,306 @@ def encumber_license(self, adverse_action: AdverseActionData) -> None: logger.info('Set encumbrance for license record') + def create_investigation(self, investigation: InvestigationData) -> None: + """ + Creates an investigation record for a provider in a jurisdiction. + + This will also update the record to have an investigationStatus of 'underInvestigation', + add an update record to show the investigation event. + + :param InvestigationData investigation: The details of the investigation to be added to the records + :raises CCNotFoundException: If the record is not found + """ + with logger.append_context_keys( + compact=investigation.compact, + provider_id=investigation.providerId, + jurisdiction=investigation.jurisdiction, + license_type_abbreviation=investigation.licenseTypeAbbreviation, + ): + # Get the record (privilege or license) + record_type = investigation.investigationAgainst + + # Query for the record (privilege or license) and all its investigations in a single query + provider_records = self.get_provider_user_records( + compact=investigation.compact, provider_id=investigation.providerId, consistent_read=True + ) + + # Separate the main record from investigation records + if investigation.investigationAgainst == InvestigationAgainstEnum.LICENSE: + record = provider_records.get_specific_license_record( + investigation.jurisdiction, investigation.licenseTypeAbbreviation + ) + if not record: + message = f'{record_type.title()} not found for jurisdiction' + logger.info(message) + raise CCNotFoundException( + f'{record_type.title()} not found for jurisdiction {investigation.jurisdiction}' + ) + + update_data_type = LicenseUpdateData + update_type = ProviderRecordType.LICENSE_UPDATE + else: + record = provider_records.get_specific_privilege_record( + investigation.jurisdiction, investigation.licenseTypeAbbreviation + ) + if not record: + message = f'{record_type.title()} not found for jurisdiction' + logger.info(message) + raise CCNotFoundException( + f'{record_type.title()} not found for jurisdiction {investigation.jurisdiction}' + ) + + update_data_type = PrivilegeUpdateData + update_type = ProviderRecordType.PRIVILEGE_UPDATE + + investigation_details = { + 'investigationId': investigation.investigationId, + } + + # Create the update record + update_record = update_data_type.create_new( + { + 'type': update_type, + 'updateType': UpdateCategory.INVESTIGATION, + 'providerId': investigation.providerId, + 'compact': investigation.compact, + 'jurisdiction': investigation.jurisdiction, + 'createDate': investigation.creationDate, + 'effectiveDate': investigation.creationDate, + 'licenseType': investigation.licenseType, + 'previous': record.to_dict(), + 'updatedValues': { + 'investigationStatus': InvestigationStatusEnum.UNDER_INVESTIGATION, + }, + 'investigationDetails': investigation_details, + } + ) + + # Prepare the transaction items + serialized_record = record.serialize_to_database_record() + transaction_items = [ + self._generate_put_transaction_item(investigation.serialize_to_database_record()), + self._generate_put_transaction_item(update_record.serialize_to_database_record()), + { + 'Update': { + 'TableName': self.config.provider_table.table_name, + 'Key': { + 'pk': {'S': serialized_record['pk']}, + 'sk': {'S': serialized_record['sk']}, + }, + 'UpdateExpression': ( + 'SET investigationStatus = :investigationStatus, dateOfUpdate = :dateOfUpdate' + ), + 'ConditionExpression': 'attribute_exists(pk)', + 'ExpressionAttributeValues': { + ':investigationStatus': {'S': InvestigationStatusEnum.UNDER_INVESTIGATION}, + ':dateOfUpdate': {'S': investigation.creationDate.isoformat()}, + }, + } + }, + ] + + # Execute the transaction + self.config.dynamodb_client.transact_write_items(TransactItems=transaction_items) + + logger.info(f'Set investigation for {record_type} record') + + def close_investigation( + self, + compact: str, + provider_id: UUID, + jurisdiction: str, + license_type_abbreviation: str, + investigation_id: UUID, + closing_user: str, + close_date: datetime, + investigation_against: InvestigationAgainstEnum, + resulting_encumbrance_id: UUID = None, + ) -> None: + """ + Closes an investigation by updating the investigation record. + + Only removes the investigation status and creates an update record if this is the last open investigation. + + :param compact: The compact name + :param provider_id: The provider ID + :param jurisdiction: The jurisdiction + :param license_type_abbreviation: The license type abbreviation + :param investigation_id: The investigation ID + :param closing_user: The user who closed the investigation + :param close_date: The date that the investigation was closed + :param investigation_against: Whether investigating a privilege or license + :param resulting_encumbrance_id: Optional encumbrance ID to reference in the investigation closure + """ + with logger.append_context_keys( + compact=compact, + provider_id=provider_id, + jurisdiction=jurisdiction, + license_type_abbreviation=license_type_abbreviation, + investigation_id=investigation_id, + ): + record_type = investigation_against.value + + # Query for the record (privilege or license) and all its investigations in a single query + provider_records = self.get_provider_user_records( + compact=compact, provider_id=provider_id, consistent_read=True + ) + + # Separate the main record from investigation records + if investigation_against == InvestigationAgainstEnum.LICENSE: + record = provider_records.get_specific_license_record(jurisdiction, license_type_abbreviation) + if not record: + message = f'{record_type.title()} not found for jurisdiction' + logger.info(message) + raise CCNotFoundException(f'{record_type.title()} not found for jurisdiction {jurisdiction}') + + update_data_type = LicenseUpdateData + update_type = ProviderRecordType.LICENSE_UPDATE + # Count open investigations (those without closeDate), excluding the one we're closing + open_investigations = provider_records.get_investigation_records_for_license( + jurisdiction, + license_type_abbreviation, + filter_condition=lambda inv: inv.investigationId != investigation_id, + ) + investigation = next( + ( + inv + for inv in provider_records.get_investigation_records_for_license( + jurisdiction, + license_type_abbreviation, + filter_condition=lambda inv: inv.investigationId == investigation_id, + ) + ), + None, + ) + else: + record = provider_records.get_specific_privilege_record(jurisdiction, license_type_abbreviation) + if not record: + message = f'{record_type.title()} not found for jurisdiction' + logger.info(message) + raise CCNotFoundException(f'{record_type.title()} not found for jurisdiction {jurisdiction}') + + update_data_type = PrivilegeUpdateData + update_type = ProviderRecordType.PRIVILEGE_UPDATE + # Count open investigations (those without closeDate), excluding the one we're closing + open_investigations = provider_records.get_investigation_records_for_privilege( + jurisdiction, + license_type_abbreviation, + filter_condition=lambda inv: inv.closeDate is None and inv.investigationId != investigation_id, + ) + investigation = next( + ( + inv + for inv in provider_records.get_investigation_records_for_privilege( + jurisdiction, + license_type_abbreviation, + filter_condition=lambda inv: inv.investigationId == investigation_id, + ) + ), + None, + ) + + if investigation is None: + raise CCNotFoundException('Investigation not found') + + # Determine if this is the last open investigation + is_last_open_investigation = len(open_investigations) == 0 + + # Prepare the transaction items + # Build the investigation update expression and values + investigation_update_expression = ( + 'SET closeDate = :closeDate, closingUser = :closingUser, dateOfUpdate = :dateOfUpdate' + ) + investigation_expression_values = { + ':closeDate': {'S': close_date.isoformat()}, + ':closingUser': {'S': closing_user}, + ':dateOfUpdate': {'S': close_date.isoformat()}, + } + + # Add resultingEncumbranceId if an encumbrance was created + if resulting_encumbrance_id: + investigation_update_expression += ', resultingEncumbranceId = :resultingEncumbranceId' + investigation_expression_values[':resultingEncumbranceId'] = {'S': str(resulting_encumbrance_id)} + + # Always update the investigation record itself + transaction_items = [ + { + 'Update': { + 'TableName': self.config.provider_table.table_name, + 'Key': { + 'pk': {'S': investigation.pk}, + 'sk': {'S': investigation.sk}, + }, + 'UpdateExpression': investigation_update_expression, + 'ConditionExpression': 'attribute_exists(pk) AND attribute_not_exists(closeDate)', + 'ExpressionAttributeValues': investigation_expression_values, + } + }, + ] + + # Only create update record and remove status if this is the last open investigation + if is_last_open_investigation: + # Create the update record for investigation closure + update_record = update_data_type.create_new( + { + 'type': update_type, + 'updateType': UpdateCategory.CLOSING_INVESTIGATION, + 'providerId': provider_id, + 'compact': compact, + 'jurisdiction': jurisdiction, + 'createDate': close_date, + 'effectiveDate': close_date, + 'licenseType': record.licenseType, + 'previous': record.to_dict(), + 'updatedValues': {}, + 'removedValues': ['investigationStatus'], + } + ) + + serialized_record = record.serialize_to_database_record() + transaction_items.extend( + [ + self._generate_put_transaction_item(update_record.serialize_to_database_record()), + # Remove investigationStatus from the license/privilege record + { + 'Update': { + 'TableName': self.config.provider_table.table_name, + 'Key': { + 'pk': {'S': serialized_record['pk']}, + 'sk': {'S': serialized_record['sk']}, + }, + 'UpdateExpression': 'REMOVE investigationStatus SET dateOfUpdate = :dateOfUpdate', + 'ConditionExpression': 'attribute_exists(pk)', + 'ExpressionAttributeValues': { + ':dateOfUpdate': {'S': close_date.isoformat()}, + }, + } + }, + ] + ) + + # Execute the transaction + try: + self.config.dynamodb_client.transact_write_items(TransactItems=transaction_items) + except Exception as e: + # Check if this is a TransactionCanceledException with ConditionalCheckFailed + if hasattr(e, 'response') and e.response.get('CancellationReasons'): + for reason in e.response['CancellationReasons']: + if reason.get('Code') == 'ConditionalCheckFailed': + logger.info('Investigation not found or already closed') + raise CCNotFoundException(f'Investigation not found: {investigation_id}') from e + # Re-raise if it's not a conditional check failure + raise + + logger.info(f'Closed investigation for {record_type} record') + def lift_privilege_encumbrance( self, compact: str, - provider_id: str, + provider_id: UUID, jurisdiction: str, license_type_abbreviation: str, - adverse_action_id: str, + adverse_action_id: UUID, effective_lift_date: date, lifting_user: str, ) -> None: @@ -1725,7 +2017,7 @@ def lift_privilege_encumbrance( # Create privilege update record privilege_update_record = PrivilegeUpdateData.create_new( { - 'type': 'privilegeUpdate', + 'type': ProviderRecordType.PRIVILEGE_UPDATE, 'updateType': UpdateCategory.LIFTING_ENCUMBRANCE, 'providerId': provider_id, 'compact': compact, @@ -1758,10 +2050,10 @@ def lift_privilege_encumbrance( def lift_license_encumbrance( self, compact: str, - provider_id: str, + provider_id: UUID, jurisdiction: str, license_type_abbreviation: str, - adverse_action_id: str, + adverse_action_id: UUID, effective_lift_date: date, lifting_user: str, ) -> None: @@ -1773,7 +2065,7 @@ def lift_license_encumbrance( :param str provider_id: The provider ID :param str jurisdiction: The jurisdiction :param str license_type_abbreviation: The license type abbreviation - :param str adverse_action_id: The adverse action ID to lift + :param UUID adverse_action_id: The adverse action ID to lift :param date effective_lift_date: The effective date when the encumbrance is lifted :param str lifting_user: The cognito sub of the user lifting the encumbrance :raises CCNotFoundException: If the adverse action record is not found @@ -2243,7 +2535,7 @@ def _get_provider_record_transaction_items_for_jurisdiction_with_no_known_licens # Create the provider update record provider_update_record = ProviderUpdateData.create_new( { - 'type': 'providerUpdate', + 'type': ProviderRecordType.PROVIDER_UPDATE, 'updateType': UpdateCategory.HOME_JURISDICTION_CHANGE, 'providerId': provider_id, 'compact': compact, @@ -2327,7 +2619,7 @@ def _get_privilege_deactivation_transaction_items_for_jurisdiction_change( # Create update record privilege_update_record = PrivilegeUpdateData.create_new( { - 'type': 'privilegeUpdate', + 'type': ProviderRecordType.PRIVILEGE_UPDATE, 'updateType': UpdateCategory.HOME_JURISDICTION_CHANGE, 'providerId': provider_id, 'compact': compact, @@ -2403,7 +2695,7 @@ def _get_provider_record_transaction_items_for_jurisdiction_change_with_license( # Create the provider update record provider_update_record = ProviderUpdateData.create_new( { - 'type': 'providerUpdate', + 'type': ProviderRecordType.PROVIDER_UPDATE, 'updateType': UpdateCategory.HOME_JURISDICTION_CHANGE, 'providerId': provider_id, 'compact': compact, @@ -2547,7 +2839,7 @@ def _get_privilege_update_transaction_items_for_jurisdiction_change( # Create update record privilege_update_record = PrivilegeUpdateData.create_new( { - 'type': 'privilegeUpdate', + 'type': ProviderRecordType.PRIVILEGE_UPDATE, 'updateType': UpdateCategory.HOME_JURISDICTION_CHANGE, 'providerId': provider_id, 'compact': compact, @@ -2705,7 +2997,7 @@ def encumber_home_jurisdiction_license_privileges( # Create privilege update record privilege_update_record = PrivilegeUpdateData.create_new( { - 'type': 'privilegeUpdate', + 'type': ProviderRecordType.PRIVILEGE_UPDATE, 'updateType': UpdateCategory.ENCUMBRANCE, 'providerId': provider_id, 'compact': compact, @@ -2738,7 +3030,7 @@ def encumber_home_jurisdiction_license_privileges( # Create privilege update record privilege_update_record = PrivilegeUpdateData.create_new( { - 'type': 'privilegeUpdate', + 'type': ProviderRecordType.PRIVILEGE_UPDATE, 'updateType': UpdateCategory.ENCUMBRANCE, 'providerId': provider_id, 'compact': compact, @@ -2860,7 +3152,7 @@ def lift_home_jurisdiction_license_privilege_encumbrances( # Create privilege update record using the latest effective lift date privilege_update_record = PrivilegeUpdateData.create_new( { - 'type': 'privilegeUpdate', + 'type': ProviderRecordType.PRIVILEGE_UPDATE, 'updateType': UpdateCategory.LIFTING_ENCUMBRANCE, 'providerId': provider_id, 'compact': compact, @@ -2951,7 +3243,7 @@ def deactivate_license_privileges( # Create privilege update record privilege_update_record = PrivilegeUpdateData.create_new( { - 'type': 'privilegeUpdate', + 'type': ProviderRecordType.PRIVILEGE_UPDATE, 'updateType': UpdateCategory.LICENSE_DEACTIVATION, 'providerId': provider_id, 'compact': compact, @@ -3096,7 +3388,7 @@ def complete_provider_email_update( # Create provider update record to track the email change provider_update_record = ProviderUpdateData.create_new( { - 'type': 'providerUpdate', + 'type': ProviderRecordType.PROVIDER_UPDATE, 'updateType': UpdateCategory.EMAIL_CHANGE, 'providerId': provider_id, 'compact': compact, 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 5ce44325a..6cf5b5c72 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 @@ -17,6 +17,7 @@ PrivilegeEncumberedStatusEnum, UpdateCategory, ) +from cc_common.data_model.schema.investigation import InvestigationData from cc_common.data_model.schema.license import LicenseData, LicenseUpdateData from cc_common.data_model.schema.military_affiliation import MilitaryAffiliationData from cc_common.data_model.schema.privilege import PrivilegeData, PrivilegeUpdateData @@ -38,6 +39,7 @@ class ProviderRecordType(StrEnum): PRIVILEGE_UPDATE = 'privilegeUpdate' MILITARY_AFFILIATION = 'militaryAffiliation' ADVERSE_ACTION = 'adverseAction' + INVESTIGATION = 'investigation' # The following update event types are used during events which caused @@ -255,7 +257,14 @@ def get_enriched_history_with_synthetic_updates_from_privilege( :param history: The raw history records we intend to extrapolate from :return: The enriched privilege history """ - create_date_sorted_original_history = sorted(history, key=lambda x: x['createDate']) + + # We don't ever serve investigation updates via the API - they're only for internal change history tracking + history_without_investigations = [ + update + for update in history + if update['updateType'] not in (UpdateCategory.INVESTIGATION, UpdateCategory.CLOSING_INVESTIGATION) + ] + create_date_sorted_original_history = sorted(history_without_investigations, key=lambda x: x['createDate']) # Inject issuance event enriched_history = [ @@ -415,6 +424,7 @@ def __init__(self, provider_records: Iterable[dict]): self._privilege_records: list[PrivilegeData] = [] self._license_records: list[LicenseData] = [] self._adverse_action_records: list[AdverseActionData] = [] + self._investigation_records: list[InvestigationData] = [] self._provider_records: list[ProviderData] = [] self._provider_update_records: list[ProviderUpdateData] = [] self._military_affiliation_records: list[MilitaryAffiliationData] = [] @@ -430,6 +440,8 @@ def __init__(self, provider_records: Iterable[dict]): self._license_records.append(LicenseData.from_database_record(record)) elif record_type == ProviderRecordType.ADVERSE_ACTION: self._adverse_action_records.append(AdverseActionData.from_database_record(record)) + elif record_type == ProviderRecordType.INVESTIGATION: + self._investigation_records.append(InvestigationData.from_database_record(record)) elif record_type == ProviderRecordType.PROVIDER: self._provider_records.append(ProviderData.from_database_record(record)) elif record_type == ProviderRecordType.PROVIDER_UPDATE: @@ -602,6 +614,62 @@ def get_adverse_action_records_for_privilege( and (filter_condition is None or filter_condition(record)) ] + def get_investigation_records_for_privilege( + self, + privilege_jurisdiction: str, + privilege_license_type_abbreviation: str, + filter_condition: Callable[[InvestigationData], bool] | None = None, + include_closed: bool = False, + ) -> list[InvestigationData]: + """ + Get all investigation records for a given privilege. + + :param privilege_jurisdiction: The jurisdiction of the privilege + :param privilege_license_type_abbreviation: The license type abbreviation + :param filter_condition: Optional filter function to apply to records + :param include_closed: If True, include closed investigations; otherwise only return active ones + :returns: List of investigation records matching the criteria + """ + return [ + record + for record in self._investigation_records + if record.investigationAgainst == 'privilege' + and record.jurisdiction == privilege_jurisdiction + and record.licenseTypeAbbreviation == privilege_license_type_abbreviation + and ( + include_closed or record.closeDate is None + ) # Only return active investigations unless include_closed is True + and (filter_condition is None or filter_condition(record)) + ] + + def get_investigation_records_for_license( + self, + license_jurisdiction: str, + license_type_abbreviation: str, + filter_condition: Callable[[InvestigationData], bool] | None = None, + include_closed: bool = False, + ) -> list[InvestigationData]: + """ + Get all investigation records for a given license. + + :param license_jurisdiction: The jurisdiction of the license + :param license_type_abbreviation: The license type abbreviation + :param filter_condition: Optional filter function to apply to records + :param include_closed: If True, include closed investigations; otherwise only return active ones + :returns: List of investigation records matching the criteria + """ + return [ + record + for record in self._investigation_records + if record.investigationAgainst == 'license' + and record.jurisdiction == license_jurisdiction + and record.licenseTypeAbbreviation == license_type_abbreviation + and ( + include_closed or record.closeDate is None + ) # Only return active investigations unless include_closed is True + and (filter_condition is None or filter_condition(record)) + ] + def get_provider_record(self) -> ProviderData: """ Get the provider record from a list of records associated with a provider. @@ -743,7 +811,7 @@ def generate_api_response_object(self) -> dict: privileges = [] military_affiliations = [record.to_dict() for record in self._military_affiliation_records] - # Build licenses dict with history and adverseActions + # Build licenses dict with investigations and adverseActions for license_record in self._license_records: license_dict = license_record.to_dict() # Note that we do not add synthetic expiration events for license records like we do privileges. @@ -761,9 +829,15 @@ def generate_api_response_object(self) -> dict: license_record.jurisdiction, license_record.licenseTypeAbbreviation ) ] + license_dict['investigations'] = [ + rec.to_dict() + for rec in self.get_investigation_records_for_license( + license_record.jurisdiction, license_record.licenseTypeAbbreviation + ) + ] licenses.append(license_dict) - # Build privileges dict with history and adverseActions + # Build privileges dict with investigations and adverseActions for privilege_record in self._privilege_records: privilege_dict = privilege_record.to_dict() privilege_updates = self.get_update_records_for_privilege( @@ -776,6 +850,12 @@ def generate_api_response_object(self) -> dict: privilege_record.jurisdiction, privilege_record.licenseTypeAbbreviation ) ] + privilege_dict['investigations'] = [ + rec.to_dict() + for rec in self.get_investigation_records_for_privilege( + privilege_record.jurisdiction, privilege_record.licenseTypeAbbreviation + ) + ] active_since = ProviderRecordUtility.calculate_privilege_active_since_date( privilege_record, privilege_updates ) 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 27d3fa4d4..ced223d8a 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 @@ -284,12 +284,23 @@ class AdverseActionAgainstEnum(StrEnum): LICENSE = 'license' +class InvestigationAgainstEnum(StrEnum): + """ + Enum for possible records that investigations can be made against + """ + + PRIVILEGE = 'privilege' + LICENSE = 'license' + + class UpdateCategory(CCEnum): DEACTIVATION = 'deactivation' EXPIRATION = 'expiration' ISSUANCE = 'issuance' RENEWAL = 'renewal' ENCUMBRANCE = 'encumbrance' + INVESTIGATION = 'investigation' + CLOSING_INVESTIGATION = 'closingInvestigation' HOME_JURISDICTION_CHANGE = 'homeJurisdictionChange' REGISTRATION = 'registration' LIFTING_ENCUMBRANCE = 'lifting_encumbrance' @@ -323,6 +334,11 @@ class PrivilegeEncumberedStatusEnum(CCEnum): LICENSE_ENCUMBERED = 'licenseEncumbered' +class InvestigationStatusEnum(CCEnum): + UNDER_INVESTIGATION = 'underInvestigation' + NOT_UNDER_INVESTIGATION = 'notUnderInvestigation' + + class HomeJurisdictionChangeStatusEnum(CCEnum): """ This is only used if the provider has existing privileges when they change their home jurisdiction, 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 0fc2cb6cd..6549399e7 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 @@ -54,6 +54,15 @@ class EncumbranceEventDetailSchema(DataEventDetailBaseSchema): adverseActionCategory = String(required=False, allow_none=False) +class InvestigationEventDetailSchema(DataEventDetailBaseSchema): + providerId = UUID(required=True, allow_none=False) + investigationId = UUID(required=True, allow_none=False) + licenseTypeAbbreviation = String(required=True, allow_none=False) + investigationAgainst = String(required=True, allow_none=False) + # Only present for investigationClosed events with encumbrance + adverseActionId = UUID(required=False, allow_none=False) + + class LicenseDeactivationDetailSchema(DataEventDetailBaseSchema): providerId = UUID(required=True, allow_none=False) licenseType = String(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 759fe2764..eb4675bfa 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 @@ -1,5 +1,5 @@ from marshmallow.fields import Decimal, List, String -from marshmallow.validate import OneOf, Range, Regexp +from marshmallow.validate import OneOf, Range, Regexp, Validator from cc_common.config import config from cc_common.data_model.schema.common import ( @@ -8,6 +8,8 @@ CompactEligibilityStatus, EncumbranceType, HomeJurisdictionChangeStatusEnum, + InvestigationAgainstEnum, + InvestigationStatusEnum, LicenseDeactivatedStatusEnum, LicenseEncumberedStatusEnum, PrivilegeEncumberedStatusEnum, @@ -67,7 +69,11 @@ def __init__(self, *args, **kwargs): class UpdateType(String): def __init__(self, *args, **kwargs): - super().__init__(*args, validate=OneOf([entry.value for entry in UpdateCategory]), **kwargs) + # Merge any provided validators with our new desired one + validate = kwargs.pop('validate', []) + if isinstance(validate, Validator): + validate = [validate] + super().__init__(*args, validate=[OneOf([entry.value for entry in UpdateCategory]), *validate], **kwargs) class LicenseEncumberedStatusField(String): @@ -80,6 +86,11 @@ def __init__(self, *args, **kwargs): super().__init__(*args, validate=OneOf([entry.value for entry in PrivilegeEncumberedStatusEnum]), **kwargs) +class InvestigationStatusField(String): + def __init__(self, *args, **kwargs): + super().__init__(*args, validate=OneOf([entry.value for entry in InvestigationStatusEnum]), **kwargs) + + class HomeJurisdictionChangeStatusField(String): def __init__(self, *args, **kwargs): super().__init__(*args, validate=OneOf([entry.value for entry in HomeJurisdictionChangeStatusEnum]), **kwargs) @@ -116,6 +127,11 @@ def __init__(self, *args, **kwargs): super().__init__(*args, validate=OneOf([entry.value for entry in ClinicalPrivilegeActionCategory]), **kwargs) +class InvestigationAgainstField(String): + def __init__(self, *args, **kwargs): + super().__init__(*args, validate=OneOf([entry.value for entry in InvestigationAgainstEnum]), **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/investigation/__init__.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/investigation/__init__.py new file mode 100644 index 000000000..90c933ff1 --- /dev/null +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/investigation/__init__.py @@ -0,0 +1,156 @@ +# ruff: noqa: N802 we use camelCase to match the marshmallow schema definition +from datetime import datetime +from uuid import UUID + +from cc_common.data_model.schema.common import ( + CCDataClass, + InvestigationAgainstEnum, +) +from cc_common.data_model.schema.investigation.record import InvestigationRecordSchema + + +class InvestigationData(CCDataClass): + """ + Class representing an Investigation with getters and setters for all properties. + Takes a dict as an argument to the constructor to avoid primitive obsession. + """ + + # Define record schema at the class level + _record_schema = InvestigationRecordSchema() + + # Can use setters to set field data + _requires_data_at_construction = False + + @staticmethod + def generate_pk(compact: str, provider_id: UUID): + return f'{compact}#PROVIDER#{provider_id}' + + @staticmethod + def generate_sk( + compact: str, + investigation_against: InvestigationAgainstEnum, + jurisdiction: str, + license_type_abbr: str, + investigation_id: UUID, + ): + return ( + f'{compact}#PROVIDER#{investigation_against}/{jurisdiction}/{license_type_abbr}#' + f'INVESTIGATION#{investigation_id}' + ) + + @property + def pk(self): + if self.compact is None: + raise ValueError('Cannot calculate pk if compact is not set') + if self.providerId is None: + raise ValueError('Cannot calculate pk if providerId is not set') + + return self.generate_pk(self.compact, self.providerId) + + @property + def sk(self): + if self.compact is None: + raise ValueError('Cannot calculate sk if compact is not set') + if self.investigationAgainst is None: + raise ValueError('Cannot calculate sk if investigationAgainst is not set') + if self.jurisdiction is None: + raise ValueError('Cannot calculate sk if jurisdiction is not set') + if self.licenseTypeAbbreviation is None: + raise ValueError('Cannot calculate sk if licenseType is not set') + if self.investigationId is None: + raise ValueError('Cannot calculate sk if investigationId is not set') + return self.generate_sk( + compact=self.compact, + investigation_against=self.investigationAgainst, + jurisdiction=self.jurisdiction, + license_type_abbr=self.licenseTypeAbbreviation, + investigation_id=self.investigationId, + ) + + @property + def compact(self) -> str: + return self._data['compact'] + + @compact.setter + def compact(self, value: str) -> None: + self._data['compact'] = value + + @property + def providerId(self) -> UUID: + return self._data['providerId'] + + @providerId.setter + def providerId(self, value: UUID) -> None: + self._data['providerId'] = value + + @property + def jurisdiction(self) -> str: + return self._data['jurisdiction'] + + @jurisdiction.setter + def jurisdiction(self, value: str) -> None: + self._data['jurisdiction'] = value + + @property + def licenseType(self) -> str: + return self._data['licenseType'] + + @licenseType.setter + def licenseType(self, value: str) -> None: + self._data['licenseType'] = value + + @property + def investigationAgainst(self) -> str: + return self._data['investigationAgainst'] + + @investigationAgainst.setter + def investigationAgainst(self, investigation_against_enum: InvestigationAgainstEnum) -> None: + self._data['investigationAgainst'] = investigation_against_enum.value + + @property + def investigationId(self) -> UUID: + return self._data['investigationId'] + + @investigationId.setter + def investigationId(self, value: UUID) -> None: + self._data['investigationId'] = value + + @property + def submittingUser(self) -> UUID: + return self._data['submittingUser'] + + @submittingUser.setter + def submittingUser(self, value: UUID) -> None: + self._data['submittingUser'] = value + + @property + def creationDate(self) -> datetime: + return self._data['creationDate'] + + @creationDate.setter + def creationDate(self, value: datetime) -> None: + self._data['creationDate'] = value + + @property + def closeDate(self) -> datetime | None: + return self._data.get('closeDate') + + @closeDate.setter + def closeDate(self, value: datetime) -> None: + self._data['closeDate'] = value + + @property + def closingUser(self) -> UUID | None: + return self._data.get('closingUser') + + @closingUser.setter + def closingUser(self, value: UUID) -> None: + self._data['closingUser'] = value + + @property + def resultingEncumbranceId(self) -> UUID | None: + return self._data.get('resultingEncumbranceId') + + @resultingEncumbranceId.setter + def resultingEncumbranceId(self, value: UUID) -> None: + self._data['resultingEncumbranceId'] = value diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/investigation/api.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/investigation/api.py new file mode 100644 index 000000000..5a0340576 --- /dev/null +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/investigation/api.py @@ -0,0 +1,45 @@ +# ruff: noqa: N801, N815 invalid-name +from marshmallow.fields import Nested, Raw, String +from marshmallow.validate import OneOf + +from cc_common.data_model.schema.adverse_action.api import AdverseActionPostRequestSchema +from cc_common.data_model.schema.base_record import ForgivingSchema +from cc_common.data_model.schema.fields import ( + Compact, + Jurisdiction, +) + + +class InvestigationPatchRequestSchema(ForgivingSchema): + """ + Schema for investigation PATCH requests (investigation closing). + + This schema is used to validate incoming requests to the investigation PATCH API endpoint + for closing investigations. + + Serialization direction: + API -> load() -> Python + """ + + # Optional encumbrance data to create when closing investigation + encumbrance = Nested(AdverseActionPostRequestSchema, required=False, allow_none=False) + + +class InvestigationGeneralResponseSchema(ForgivingSchema): + """ + Schema for investigation general responses. + + Serialization direction: + Python -> load() -> API + """ + + type = String(required=True, allow_none=False, validate=OneOf(['investigation'])) + compact = Compact(required=True, allow_none=False) + providerId = Raw(required=True, allow_none=False) + investigationId = Raw(required=True, allow_none=False) + jurisdiction = Jurisdiction(required=True, allow_none=False) + licenseType = String(required=True, allow_none=False) + dateOfUpdate = Raw(required=True, allow_none=False) + + creationDate = Raw(required=True, allow_none=False) + submittingUser = Raw(required=True, allow_none=False) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/investigation/record.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/investigation/record.py new file mode 100644 index 000000000..ee057782a --- /dev/null +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/investigation/record.py @@ -0,0 +1,66 @@ +# ruff: noqa: N801, N815 invalid-name +from marshmallow import Schema, ValidationError, pre_dump +from marshmallow.fields import UUID, DateTime, String + +from cc_common.config import config +from cc_common.data_model.schema.base_record import BaseRecordSchema +from cc_common.data_model.schema.common import ValidatesLicenseTypeMixin +from cc_common.data_model.schema.fields import ( + Compact, + InvestigationAgainstField, + Jurisdiction, +) + + +@BaseRecordSchema.register_schema('investigation') +class InvestigationRecordSchema(BaseRecordSchema, ValidatesLicenseTypeMixin): + """ + Schema for investigation records in the provider data table + + Serialization direction: + DB -> load() -> Python + """ + + _record_type = 'investigation' + + compact = Compact(required=True, allow_none=False) + providerId = UUID(required=True, allow_none=False) + jurisdiction = Jurisdiction(required=True, allow_none=False) + licenseType = String(required=True, allow_none=False) + investigationAgainst = InvestigationAgainstField(required=True, allow_none=False) + + # Populated on creation + investigationId = UUID(required=True, allow_none=False) + submittingUser = UUID(required=True, allow_none=False) + creationDate = DateTime(required=True, allow_none=False) + + # Populated when the investigation is closed + closeDate = DateTime(required=False, allow_none=False) + closingUser = UUID(required=False, allow_none=False) + resultingEncumbranceId = UUID(required=False, allow_none=False) + + @pre_dump + def generate_pk_sk(self, in_data, **_kwargs): + in_data['pk'] = f'{in_data["compact"]}#PROVIDER#{in_data["providerId"]}' + # ensure this is passed in lowercase + try: + license_type_abbr = config.license_type_abbreviations[in_data['compact']][in_data['licenseType']] + except KeyError as e: + # Validation is usually done on load and this runs on dump, but we depend on this value being valid + # so we might as well raise a ValidationError if we try to dump an invalid license type + license_types = config.license_types_for_compact(in_data['compact']) + raise ValidationError({'licenseType': [f'Must be one of: {", ".join(license_types)}.']}) from e + in_data['sk'] = ( + f'{in_data["compact"]}#PROVIDER#{in_data["investigationAgainst"]}/{in_data["jurisdiction"]}/{license_type_abbr}#INVESTIGATION#{in_data["investigationId"]}' + ) + return in_data + + +class InvestigationDetailsSchema(Schema): + """ + Schema for tracking details about an investigation. + """ + + investigationId = UUID(required=True, allow_none=False) + # present if update is created by upstream license investigation + licenseJurisdiction = Jurisdiction(required=False, allow_none=False) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/__init__.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/__init__.py index 32e71ee7b..5672fd071 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/__init__.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/__init__.py @@ -136,6 +136,10 @@ def compactEligibility(self) -> str: def encumberedStatus(self) -> str | None: return self._data.get('encumberedStatus') + @property + def investigationStatus(self) -> str | None: + return self._data.get('investigationStatus') + class LicenseUpdateData(CCDataClass): """ diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/api.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/api.py index 52c7764ff..8e8c271ff 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/api.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/api.py @@ -15,11 +15,13 @@ ActiveInactive, Compact, CompactEligibility, + InvestigationStatusField, ITUTE164PhoneNumber, Jurisdiction, NationalProviderIdentifier, SocialSecurityNumber, ) +from cc_common.data_model.schema.investigation.api import InvestigationGeneralResponseSchema class LicensePostRequestSchema(CCRequestSchema, StrictSchema): @@ -145,6 +147,9 @@ class LicenseGeneralResponseSchema(ForgivingSchema): emailAddress = Email(required=False, allow_none=False) phoneNumber = ITUTE164PhoneNumber(required=False, allow_none=False) adverseActions = List(Nested(AdverseActionGeneralResponseSchema, required=False, allow_none=False)) + investigations = List(Nested(InvestigationGeneralResponseSchema, required=False, allow_none=False)) + # This field is only set if the license is under investigation + investigationStatus = InvestigationStatusField(required=False, allow_none=False) class LicenseReadPrivateResponseSchema(ForgivingSchema): @@ -183,6 +188,9 @@ class LicenseReadPrivateResponseSchema(ForgivingSchema): emailAddress = Email(required=False, allow_none=False) phoneNumber = ITUTE164PhoneNumber(required=False, allow_none=False) adverseActions = List(Nested(AdverseActionGeneralResponseSchema, required=False, allow_none=False)) + investigations = List(Nested(InvestigationGeneralResponseSchema, required=False, allow_none=False)) + # This field is only set if the license is under investigation + investigationStatus = InvestigationStatusField(required=False, allow_none=False) # these fields are specific to the read private role dateOfBirth = Raw(required=False, allow_none=False) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/record.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/record.py index b763fed4a..9af6ea97e 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/record.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/record.py @@ -16,17 +16,20 @@ ChangeHashMixin, CompactEligibilityStatus, LicenseEncumberedStatusEnum, + UpdateCategory, ) from cc_common.data_model.schema.fields import ( ActiveInactive, Compact, CompactEligibility, + InvestigationStatusField, ITUTE164PhoneNumber, Jurisdiction, LicenseEncumberedStatusField, NationalProviderIdentifier, UpdateType, ) +from cc_common.data_model.schema.investigation.record import InvestigationDetailsSchema from cc_common.data_model.schema.license.common import LicenseCommonSchema @@ -52,6 +55,8 @@ class LicenseRecordSchema(BaseRecordSchema, LicenseCommonSchema): # optional field for setting encumbrance status encumberedStatus = LicenseEncumberedStatusField(required=False, allow_none=False) + # optional field for setting investigation status + investigationStatus = InvestigationStatusField(required=False, allow_none=False) # Persisted values jurisdictionUploadedLicenseStatus = ActiveInactive(required=True, allow_none=False) @@ -160,6 +165,8 @@ class LicenseUpdateRecordPreviousSchema(ForgivingSchema): licenseStatusName = String(required=False, allow_none=False, validate=Length(1, 100)) jurisdictionUploadedLicenseStatus = ActiveInactive(required=True, allow_none=False) jurisdictionUploadedCompactEligibility = CompactEligibility(required=True, allow_none=False) + encumberedStatus = LicenseEncumberedStatusField(required=False, allow_none=False) + investigationStatus = InvestigationStatusField(required=False, allow_none=False) @BaseRecordSchema.register_schema('licenseUpdate') @@ -187,6 +194,8 @@ class LicenseUpdateRecordSchema(BaseRecordSchema, ChangeHashMixin): effectiveDate = DateTime(required=True, allow_none=False) # We'll allow any fields that can show up in the previous field to be here as well, but none are required updatedValues = Nested(LicenseUpdateRecordPreviousSchema(partial=True), required=True, allow_none=False) + # optional field that is only included if the update was an investigation + investigationDetails = Nested(InvestigationDetailsSchema(), required=False, allow_none=False) # List of field names that were present in the previous record but removed in the update removedValues = List(String(), required=False, allow_none=False) @@ -215,3 +224,13 @@ def validate_license_type(self, data, **kwargs): # noqa: ARG001 unused-argument license_types = config.license_types_for_compact(data['compact']) if data['licenseType'] not in license_types: raise ValidationError({'licenseType': [f'Must be one of: {", ".join(license_types)}.']}) + + @validates_schema + def validate_investigation_details_present_if_investigation_status_updated(self, data, **kwargs): # noqa: ARG002 + """ + Require investigationDetails whenever update type is investigation + """ + if data['updateType'] == UpdateCategory.INVESTIGATION and not data.get('investigationDetails'): + raise ValidationError( + {'investigationDetails': ['This field is required when update was investigation type']} + ) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/__init__.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/__init__.py index b08c2ed81..5425958fd 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/__init__.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/__init__.py @@ -80,6 +80,10 @@ def homeJurisdictionChangeStatus(self) -> str | None: def licenseDeactivatedStatus(self) -> str | None: return self._data.get('licenseDeactivatedStatus') + @property + def investigationStatus(self) -> str | None: + return self._data.get('investigationStatus') + @property def status(self) -> str: """ diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/api.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/api.py index c9edcf635..659faa3a6 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/api.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/api.py @@ -1,14 +1,22 @@ # ruff: noqa: N801, N815, ARG002 invalid-name unused-argument from marshmallow import Schema from marshmallow.fields import List, Nested, Raw, String -from marshmallow.validate import Length +from marshmallow.validate import ContainsNoneOf, Length from cc_common.data_model.schema.adverse_action.api import ( AdverseActionGeneralResponseSchema, AdverseActionPublicResponseSchema, ) from cc_common.data_model.schema.base_record import ForgivingSchema -from cc_common.data_model.schema.fields import ActiveInactive, Compact, Jurisdiction, UpdateType +from cc_common.data_model.schema.common import UpdateCategory +from cc_common.data_model.schema.fields import ( + ActiveInactive, + Compact, + InvestigationStatusField, + Jurisdiction, + UpdateType, +) +from cc_common.data_model.schema.investigation.api import InvestigationGeneralResponseSchema class AttestationVersionResponseSchema(Schema): @@ -68,6 +76,7 @@ class PrivilegeGeneralResponseSchema(ForgivingSchema): dateOfExpiration = Raw(required=True, allow_none=False) dateOfUpdate = Raw(required=True, allow_none=False) adverseActions = List(Nested(AdverseActionGeneralResponseSchema, required=False, allow_none=False)) + investigations = List(Nested(InvestigationGeneralResponseSchema, required=False, allow_none=False)) administratorSetStatus = ActiveInactive(required=True, allow_none=False) # the id of the transaction that was made when the user purchased this privilege compactTransactionId = String(required=False, allow_none=False) @@ -79,6 +88,8 @@ class PrivilegeGeneralResponseSchema(ForgivingSchema): # This field shows how long the privilege have been continuously active according to # its history activeSince = Raw(required=False, allow_none=False) + # This field is only set if the privilege is under investigation + investigationStatus = InvestigationStatusField(required=False, allow_none=False) class PrivilegeReadPrivateResponseSchema(ForgivingSchema): @@ -100,6 +111,7 @@ class PrivilegeReadPrivateResponseSchema(ForgivingSchema): dateOfExpiration = Raw(required=True, allow_none=False) dateOfUpdate = Raw(required=True, allow_none=False) adverseActions = List(Nested(AdverseActionGeneralResponseSchema, required=False, allow_none=False)) + investigations = List(Nested(InvestigationGeneralResponseSchema, required=False, allow_none=False)) administratorSetStatus = ActiveInactive(required=True, allow_none=False) # the id of the transaction that was made when the user purchased this privilege compactTransactionId = String(required=False, allow_none=False) @@ -111,6 +123,8 @@ class PrivilegeReadPrivateResponseSchema(ForgivingSchema): # This field shows how long the privilege have been continuously active according to # its history activeSince = Raw(required=False, allow_none=False) + # This field is only set if the privilege is under investigation + investigationStatus = InvestigationStatusField(required=False, allow_none=False) # these fields are specific to the read private role dateOfBirth = Raw(required=False, allow_none=False) @@ -153,7 +167,13 @@ class PrivilegeHistoryEventResponseSchema(ForgivingSchema): """ type = String(required=True, allow_none=False) - updateType = UpdateType(required=True, allow_none=False) + # We specifically prohibit returning investigation updates as a backup protection from accidental + # disclosure via the API + updateType = UpdateType( + required=True, + allow_none=False, + validate=ContainsNoneOf((UpdateCategory.INVESTIGATION, UpdateCategory.CLOSING_INVESTIGATION)), + ) dateOfUpdate = Raw(required=True, allow_none=False) effectiveDate = Raw(required=True, allow_none=False) createDate = Raw(required=True, allow_none=False) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py index cfacf242c..fdc918357 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py @@ -20,11 +20,13 @@ ClinicalPrivilegeActionCategoryField, Compact, HomeJurisdictionChangeStatusField, + InvestigationStatusField, Jurisdiction, LicenseDeactivatedStatusField, PrivilegeEncumberedStatusField, UpdateType, ) +from cc_common.data_model.schema.investigation.record import InvestigationDetailsSchema class AttestationVersionRecordSchema(Schema): @@ -97,6 +99,8 @@ class PrivilegeRecordSchema(BaseRecordSchema, ValidatesLicenseTypeMixin): # this field is only set if the privilege or the associated license is encumbered encumberedStatus = PrivilegeEncumberedStatusField(required=False, allow_none=False) + # this field is only set if the privilege is under investigation + investigationStatus = InvestigationStatusField(required=False, allow_none=False) # This field is only set if a privilege is deactivated as a result of a provider changing their home jurisdiction # It is removed in the event that the provider is able to repurchase the privilege in the new jurisdiction after @@ -190,6 +194,8 @@ class PrivilegeUpdatePreviousRecordSchema(ForgivingSchema): homeJurisdictionChangeStatus = HomeJurisdictionChangeStatusField(required=False, allow_none=False) # this field is only set if the privilege or the associated license is encumbered encumberedStatus = PrivilegeEncumberedStatusField(required=False, allow_none=False) + # this field is only set if the privilege is under investigation + investigationStatus = InvestigationStatusField(required=False, allow_none=False) # this field is only set if the privilege is deactivated due to a state license deactivation licenseDeactivatedStatus = LicenseDeactivatedStatusField(required=False, allow_none=False) @@ -220,6 +226,8 @@ class PrivilegeUpdateRecordSchema(BaseRecordSchema, ChangeHashMixin, ValidatesLi deactivationDetails = Nested(DeactivationDetailsSchema(), required=False, allow_none=False) # optional field that is only included if the update was an encumbrance encumbranceDetails = Nested(EncumbranceDetailsSchema(), required=False, allow_none=False) + # optional field that is only included if the update was an investigation + investigationDetails = Nested(InvestigationDetailsSchema(), required=False, allow_none=False) # List of field names that were present in the previous record but removed in the update removedValues = List(String(), required=False, allow_none=False) @@ -238,9 +246,22 @@ def generate_pk_sk(self, in_data, **kwargs): # noqa: ARG001 unused-argument @validates_schema def validate_deactivation_details_present_if_deactivation_update(self, data, **kwargs): # noqa: ARG002 unused-argument + """ + Require deactivationDetails whenever update type is deactivation + """ if data['updateType'] == UpdateCategory.DEACTIVATION and not data.get('deactivationDetails'): raise ValidationError({'deactivationDetails': ['This field is required when update was deactivation type']}) + @validates_schema + def validate_investigation_details_present_if_investigation_status_updated(self, data, **kwargs): # noqa: ARG002 + """ + Require investigationDetails whenever update type is investigation + """ + if data['updateType'] == UpdateCategory.INVESTIGATION and not data.get('investigationDetails'): + raise ValidationError( + {'investigationDetails': ['This field is required when update was investigation type']} + ) + @pre_dump def generate_compact_transaction_gsi_field(self, in_data, **kwargs): # noqa: ARG001 unused-argument """ 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 410a8e764..8c8edc6d9 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 @@ -24,6 +24,19 @@ class EncumbranceNotificationTemplateVariables: provider_id: UUID | None = None +@dataclass +class InvestigationNotificationTemplateVariables: + """ + Template variables for investigation notification emails. + """ + + provider_first_name: str + provider_last_name: str + investigation_jurisdiction: str + license_type: str + provider_id: UUID + + class ProviderNotificationMethod(Protocol): """Protocol for provider encumbrance notification methods.""" @@ -310,6 +323,9 @@ def send_license_encumbrance_state_notification_email( :param template_variables: Template variables for the email :return: Response from the email notification service """ + if template_variables.provider_id is None: + raise ValueError('Provider ID is required for state notification emails') + payload = { 'compact': compact, 'jurisdiction': jurisdiction, @@ -371,6 +387,9 @@ def send_license_encumbrance_lifting_state_notification_email( :param template_variables: Template variables for the email :return: Response from the email notification service """ + if template_variables.provider_id is None: + raise ValueError('Provider ID is required for state notification emails') + payload = { 'compact': compact, 'jurisdiction': jurisdiction, @@ -432,6 +451,9 @@ def send_privilege_encumbrance_state_notification_email( :param template_variables: Template variables for the email :return: Response from the email notification service """ + if template_variables.provider_id is None: + raise ValueError('Provider ID is required for state notification emails.') + payload = { 'compact': compact, 'jurisdiction': jurisdiction, @@ -493,6 +515,9 @@ def send_privilege_encumbrance_lifting_state_notification_email( :param template_variables: Template variables for the email :return: Response from the email notification service """ + if template_variables.provider_id is None: + raise ValueError('Provider ID is required for state notification emails.') + payload = { 'compact': compact, 'jurisdiction': jurisdiction, @@ -595,3 +620,135 @@ def send_provider_account_recovery_confirmation_email( } return self._invoke_lambda(payload) + + def send_license_investigation_state_notification_email( + self, + *, + compact: str, + jurisdiction: str, + template_variables: InvestigationNotificationTemplateVariables, + ) -> dict[str, str]: + """ + Send a license investigation notification email to a state. + + :param compact: Compact name + :param jurisdiction: Jurisdiction to notify + :param template_variables: Template variables for the email + :return: Response from the email notification service + """ + if template_variables.provider_id is None: + raise ValueError('provider_id must be provided for state notifications') + + payload = { + 'compact': compact, + 'jurisdiction': jurisdiction, + 'template': 'licenseInvestigationStateNotification', + 'recipientType': 'JURISDICTION_ADVERSE_ACTIONS', + 'templateVariables': { + 'providerFirstName': template_variables.provider_first_name, + 'providerLastName': template_variables.provider_last_name, + 'providerId': str(template_variables.provider_id), + 'investigationJurisdiction': template_variables.investigation_jurisdiction, + 'licenseType': template_variables.license_type, + }, + } + return self._invoke_lambda(payload) + + def send_license_investigation_closed_state_notification_email( + self, + *, + compact: str, + jurisdiction: str, + template_variables: InvestigationNotificationTemplateVariables, + ) -> dict[str, str]: + """ + Send a license investigation closed notification email to a state. + + :param compact: Compact name + :param jurisdiction: Jurisdiction to notify + :param template_variables: Template variables for the email + :return: Response from the email notification service + """ + if template_variables.provider_id is None: + raise ValueError('provider_id must be provided for state notifications') + + payload = { + 'compact': compact, + 'jurisdiction': jurisdiction, + 'template': 'licenseInvestigationClosedStateNotification', + 'recipientType': 'JURISDICTION_ADVERSE_ACTIONS', + 'templateVariables': { + 'providerFirstName': template_variables.provider_first_name, + 'providerLastName': template_variables.provider_last_name, + 'providerId': str(template_variables.provider_id), + 'investigationJurisdiction': template_variables.investigation_jurisdiction, + 'licenseType': template_variables.license_type, + }, + } + return self._invoke_lambda(payload) + + def send_privilege_investigation_state_notification_email( + self, + *, + compact: str, + jurisdiction: str, + template_variables: InvestigationNotificationTemplateVariables, + ) -> dict[str, str]: + """ + Send a privilege investigation notification email to a state. + + :param compact: Compact name + :param jurisdiction: Jurisdiction to notify + :param template_variables: Template variables for the email + :return: Response from the email notification service + """ + if template_variables.provider_id is None: + raise ValueError('provider_id must be provided for state notifications') + + payload = { + 'compact': compact, + 'jurisdiction': jurisdiction, + 'template': 'privilegeInvestigationStateNotification', + 'recipientType': 'JURISDICTION_ADVERSE_ACTIONS', + 'templateVariables': { + 'providerFirstName': template_variables.provider_first_name, + 'providerLastName': template_variables.provider_last_name, + 'providerId': str(template_variables.provider_id), + 'investigationJurisdiction': template_variables.investigation_jurisdiction, + 'licenseType': template_variables.license_type, + }, + } + return self._invoke_lambda(payload) + + def send_privilege_investigation_closed_state_notification_email( + self, + *, + compact: str, + jurisdiction: str, + template_variables: InvestigationNotificationTemplateVariables, + ) -> dict[str, str]: + """ + Send a privilege investigation closed notification email to a state. + + :param compact: Compact name + :param jurisdiction: Jurisdiction to notify + :param template_variables: Template variables for the email + :return: Response from the email notification service + """ + if template_variables.provider_id is None: + raise ValueError('provider_id must be provided for state notifications') + + payload = { + 'compact': compact, + 'jurisdiction': jurisdiction, + 'template': 'privilegeInvestigationClosedStateNotification', + 'recipientType': 'JURISDICTION_ADVERSE_ACTIONS', + 'templateVariables': { + 'providerFirstName': template_variables.provider_first_name, + 'providerLastName': template_variables.provider_last_name, + 'providerId': str(template_variables.provider_id), + 'investigationJurisdiction': template_variables.investigation_jurisdiction, + 'licenseType': template_variables.license_type, + }, + } + return self._invoke_lambda(payload) 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 71106346f..88ae6307d 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 @@ -1,10 +1,12 @@ import json -from datetime import date +from datetime import date, datetime from uuid import UUID from cc_common.config import config +from cc_common.data_model.schema.common import InvestigationAgainstEnum from cc_common.data_model.schema.data_event.api import ( EncumbranceEventDetailSchema, + InvestigationEventDetailSchema, LicenseDeactivationDetailSchema, PrivilegeIssuanceDetailSchema, PrivilegePurchaseEventDetailSchema, @@ -333,3 +335,105 @@ def publish_privilege_encumbrance_lifting_event( detail=deserialized_detail, event_batch_writer=event_batch_writer, ) + + def publish_investigation_event( + self, + source: str, + compact: str, + provider_id: UUID, + jurisdiction: str, + license_type_abbreviation: str, + create_date: datetime, + investigation_against: InvestigationAgainstEnum, + investigation_id: UUID, + event_batch_writer: EventBatchWriter | None = None, + ): + """ + Publish an investigation event to the event bus. + + :param source: The source of the event + :param compact: The compact name + :param provider_id: The provider ID + :param jurisdiction: The jurisdiction of the record being investigated + :param license_type_abbreviation: The license type abbreviation + :param create_date: The datetime when the investigation record was created + :param investigation_against: The type of record being investigated (privilege or license) + :param investigation_id: The investigation ID + :param event_batch_writer: Optional EventBatchWriter for efficient batch publishing + """ + event_detail = { + 'compact': compact, + 'providerId': provider_id, + 'jurisdiction': jurisdiction, + 'licenseTypeAbbreviation': license_type_abbreviation, + 'investigationAgainst': investigation_against.value, + 'investigationId': investigation_id, + 'eventTime': create_date, + } + + investigation_detail_schema = InvestigationEventDetailSchema() + deserialized_detail = investigation_detail_schema.dump(event_detail) + + # Determine the detail type based on investigation_against + detail_type = f'{investigation_against}.investigation' + + self._publish_event( + source=source, + detail_type=detail_type, + detail=deserialized_detail, + event_batch_writer=event_batch_writer, + ) + + def publish_investigation_closed_event( + self, + source: str, + compact: str, + provider_id: UUID, + jurisdiction: str, + license_type_abbreviation: str, + close_date: datetime, + investigation_against: InvestigationAgainstEnum, + investigation_id: UUID, + adverse_action_id: UUID | None = None, + event_batch_writer: EventBatchWriter | None = None, + ): + """ + Publish an investigation closed event to the event bus. + + :param source: The source of the event + :param compact: The compact name + :param provider_id: The provider ID + :param jurisdiction: The jurisdiction of the record being investigated + :param license_type_abbreviation: The license type abbreviation + :param close_date: The datetime when the investigation record was closed + :param investigation_against: The type of record being investigated (privilege or license) + :param investigation_id: The id of the investigation closed + :param adverse_action_id: Optional adverse action ID if an encumbrance resulted from the investigation + :param event_batch_writer: Optional EventBatchWriter for efficient batch publishing + """ + event_detail = { + 'compact': compact, + 'providerId': provider_id, + 'jurisdiction': jurisdiction, + 'licenseTypeAbbreviation': license_type_abbreviation, + 'investigationAgainst': investigation_against.value, + 'investigationId': investigation_id, + 'eventTime': close_date, + } + + # Include adverseActionId if an encumbrance resulted from the investigation + if adverse_action_id is not None: + event_detail['adverseActionId'] = adverse_action_id + + investigation_detail_schema = InvestigationEventDetailSchema() + deserialized_detail = investigation_detail_schema.dump(event_detail) + + # Determine the detail type based on investigation_against + detail_type = f'{investigation_against.value}.investigationClosed' + + self._publish_event( + source=source, + detail_type=detail_type, + detail=deserialized_detail, + event_batch_writer=event_batch_writer, + ) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/feature_flag_client.py b/backend/compact-connect/lambdas/python/common/cc_common/feature_flag_client.py index 0b9bcaa21..2d4264fd9 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/feature_flag_client.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/feature_flag_client.py @@ -79,7 +79,7 @@ def is_feature_enabled( ): """ try: - logger.info("checking status of feature flag", flag_name=flag_name) + logger.info('checking status of feature flag', flag_name=flag_name) api_base_url = _get_api_base_url() endpoint_url = f'{api_base_url}/v1/flags/{flag_name}/check' diff --git a/backend/compact-connect/lambdas/python/common/cc_common/utils.py b/backend/compact-connect/lambdas/python/common/cc_common/utils.py index 0f74a7883..31278927d 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/utils.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/utils.py @@ -850,6 +850,7 @@ def _get_recaptcha_secret() -> str: raise CCInternalException('Failed to load reCAPTCHA secret') from e return _RECAPTCHA_SECRET + def verify_recaptcha(token: str) -> bool: """Verify the reCAPTCHA token with Google's API.""" @@ -910,3 +911,22 @@ def verify_password(hashed_password: str, password: str) -> bool: except Exception as e: logger.error('Failed to verify password', error=str(e)) raise CCInternalException('Failed to verify password') from e + + +def to_uuid(uuid: str, on_error: str) -> UUID: + """ + Parse a str to a UUID, raising CCInvalidRequestException if invalid. + + This should be used for all UUID path parameters to validate and normalize + input before processing, preventing malformed UUIDs from causing unexpected + errors deeper in the application. + + :param str uuid: The string representation of a UUID to parse + :param str on_error: Custom error message to include in the exception + :return: A validated UUID object + :raises CCInvalidRequestException: If the string is not a valid UUID + """ + try: + return UUID(uuid) + except ValueError as e: + raise CCInvalidRequestException(on_error) from e diff --git a/backend/compact-connect/lambdas/python/common/common_test/test_constants.py b/backend/compact-connect/lambdas/python/common/common_test/test_constants.py index 4292917a7..54043c5c3 100644 --- a/backend/compact-connect/lambdas/python/common/common_test/test_constants.py +++ b/backend/compact-connect/lambdas/python/common/common_test/test_constants.py @@ -84,6 +84,12 @@ DEFAULT_AA_SUBMITTING_USER_ID = '12a6377e-c3a5-40e5-bca5-317ec854c556' DEFAULT_ADVERSE_ACTION_ID = '98765432-9876-9876-9876-987654321098' +# Investigation defaults +DEFAULT_INVESTIGATION_AGAINST_PRIVILEGE = 'privilege' +DEFAULT_INVESTIGATION_AGAINST_LICENSE = 'license' +DEFAULT_INVESTIGATION_START_DATE = '2024-02-15' +DEFAULT_INVESTIGATION_ID = '98765432-9876-9876-9876-987654321098' + # Default attestation values DEFAULT_ATTESTATIONS = [{'attestationId': 'jurisprudence-confirmation', 'version': '1'}] 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 26283462e..fd2e36e83 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 @@ -8,6 +8,7 @@ from cc_common.data_model.schema.adverse_action import AdverseActionData from cc_common.data_model.schema.common import CCDataClass from cc_common.data_model.schema.compact import CompactConfigurationData +from cc_common.data_model.schema.investigation import InvestigationData from cc_common.data_model.schema.jurisdiction import JurisdictionConfigurationData from cc_common.data_model.schema.license import LicenseData, LicenseUpdateData from cc_common.data_model.schema.military_affiliation import MilitaryAffiliationData @@ -150,6 +151,36 @@ def generate_default_adverse_action(value_overrides: dict | None = None) -> Adve return AdverseActionData.create_new(default_adverse_actions) + @staticmethod + def generate_default_investigation(value_overrides: dict | None = None) -> InvestigationData: + """Generate a default investigation""" + default_investigation = { + 'providerId': DEFAULT_PROVIDER_ID, + 'compact': DEFAULT_COMPACT, + 'type': 'investigation', + 'jurisdiction': DEFAULT_PRIVILEGE_JURISDICTION, + 'licenseTypeAbbreviation': DEFAULT_LICENSE_TYPE_ABBREVIATION, + 'licenseType': DEFAULT_LICENSE_TYPE, + 'investigationAgainst': DEFAULT_INVESTIGATION_AGAINST_PRIVILEGE, + 'createDate': date.fromisoformat(DEFAULT_INVESTIGATION_START_DATE), + 'submittingUser': DEFAULT_AA_SUBMITTING_USER_ID, + 'creationDate': datetime.fromisoformat(DEFAULT_DATE_OF_UPDATE_TIMESTAMP), + 'investigationId': DEFAULT_INVESTIGATION_ID, + } + if value_overrides: + default_investigation.update(value_overrides) + + return InvestigationData.create_new(default_investigation) + + @staticmethod + def put_default_investigation_record_in_provider_table(value_overrides: dict | None = None) -> InvestigationData: + investigation = TestDataGenerator.generate_default_investigation(value_overrides) + investigation_record = investigation.serialize_to_database_record() + + TestDataGenerator.store_record_in_provider_table(investigation_record) + + return investigation + @staticmethod def put_default_adverse_action_record_in_provider_table(value_overrides: dict | None = None) -> AdverseActionData: adverse_action = TestDataGenerator.generate_default_adverse_action(value_overrides) diff --git a/backend/compact-connect/lambdas/python/common/requirements-dev.in b/backend/compact-connect/lambdas/python/common/requirements-dev.in index 6f51419dd..676fc10bc 100644 --- a/backend/compact-connect/lambdas/python/common/requirements-dev.in +++ b/backend/compact-connect/lambdas/python/common/requirements-dev.in @@ -1,4 +1,4 @@ -moto[dynamodb, s3]>=5.0.12, <6 +moto[all]>=5.0.12, <6 boto3-stubs[full] -Faker>=28,<29 +Faker>=37, <38 cryptography>=46, <47 diff --git a/backend/compact-connect/lambdas/python/common/requirements-dev.txt b/backend/compact-connect/lambdas/python/common/requirements-dev.txt index d79ddd986..65f18cb4d 100644 --- a/backend/compact-connect/lambdas/python/common/requirements-dev.txt +++ b/backend/compact-connect/lambdas/python/common/requirements-dev.txt @@ -1,35 +1,57 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/common/requirements-dev.in +# pip-compile --no-emit-index-url --no-strip-extras lambdas/python/common/requirements-dev.in # -boto3==1.40.35 +annotated-types==0.7.0 + # via pydantic +antlr4-python3-runtime==4.13.2 # via moto -boto3-stubs[full]==1.40.35 +attrs==25.4.0 + # via + # jsonschema + # referencing +aws-sam-translator==1.101.0 + # via cfn-lint +aws-xray-sdk==2.14.0 + # via moto +boto3==1.40.56 + # via + # aws-sam-translator + # moto +boto3-stubs[full]==1.40.56 # via -r lambdas/python/common/requirements-dev.in -boto3-stubs-full==1.40.34 +boto3-stubs-full==1.40.56 # via boto3-stubs -botocore==1.40.35 +botocore==1.40.56 # via + # aws-xray-sdk # boto3 # moto # s3transfer -botocore-stubs==1.40.33 +botocore-stubs==1.40.56 # via boto3-stubs -certifi==2025.8.3 +certifi==2025.10.5 # via requests cffi==2.0.0 # via cryptography -charset-normalizer==3.4.3 - # via requests -cryptography==46.0.1 +cfn-lint==1.40.2 # via moto +charset-normalizer==3.4.4 + # via requests +cryptography==46.0.3 + # via + # -r lambdas/python/common/requirements-dev.in + # joserfc + # moto docker==7.1.0 # via moto faker==28.4.1 # via -r lambdas/python/common/requirements-dev.in -idna==3.10 +graphql-core==3.2.6 + # via moto +idna==3.11 # via requests jinja2==3.1.6 # via moto @@ -37,40 +59,111 @@ jmespath==1.0.1 # via # boto3 # botocore -markupsafe==3.0.2 +joserfc==1.4.0 + # via moto +jsonpatch==1.33 + # via cfn-lint +jsonpath-ng==1.7.0 + # via moto +jsonpointer==3.0.0 + # via jsonpatch +jsonschema==4.25.1 + # via + # aws-sam-translator + # moto + # openapi-schema-validator + # openapi-spec-validator +jsonschema-path==0.3.4 + # via openapi-spec-validator +jsonschema-specifications==2025.9.1 + # via + # jsonschema + # openapi-schema-validator +lazy-object-proxy==1.12.0 + # via openapi-spec-validator +markupsafe==3.0.3 # via # jinja2 # werkzeug -moto[dynamodb,s3]==5.1.12 +moto[all]==5.1.15 # via -r lambdas/python/common/requirements-dev.in +mpmath==1.3.0 + # via sympy +multipart==1.3.0 + # via moto +networkx==3.5 + # via cfn-lint +openapi-schema-validator==0.6.3 + # via openapi-spec-validator +openapi-spec-validator==0.7.2 + # via moto +pathable==0.4.4 + # via jsonschema-path +ply==3.11 + # via jsonpath-ng py-partiql-parser==0.6.1 # via moto pycparser==2.23 # via cffi +pydantic==2.12.3 + # via aws-sam-translator +pydantic-core==2.41.4 + # via pydantic +pyparsing==3.2.5 + # via moto python-dateutil==2.9.0.post0 # via # botocore # faker # moto -pyyaml==6.0.2 +pyyaml==6.0.3 # via + # cfn-lint + # jsonschema-path # moto # responses +referencing==0.36.2 + # via + # jsonschema + # jsonschema-path + # jsonschema-specifications +regex==2025.10.23 + # via cfn-lint requests==2.32.5 # via # docker + # jsonschema-path # moto # responses responses==0.25.8 # via moto +rfc3339-validator==0.1.4 + # via openapi-schema-validator +rpds-py==0.27.1 + # via + # jsonschema + # referencing s3transfer==0.14.0 # via boto3 six==1.17.0 - # via python-dateutil -types-awscrt==0.27.6 + # via + # python-dateutil + # rfc3339-validator +sympy==1.14.0 + # via cfn-lint +types-awscrt==0.28.2 # via botocore-stubs -types-s3transfer==0.13.1 +types-s3transfer==0.14.0 # via boto3-stubs +typing-extensions==4.15.0 + # via + # aws-sam-translator + # cfn-lint + # pydantic + # pydantic-core + # typing-inspection +typing-inspection==0.4.2 + # via pydantic urllib3==2.5.0 # via # botocore @@ -79,5 +172,10 @@ urllib3==2.5.0 # responses werkzeug==3.1.3 # via moto +wrapt==2.0.0 + # via aws-xray-sdk xmltodict==1.0.2 # via moto + +# The following packages are considered to be unsafe in a requirements file: +# setuptools diff --git a/backend/compact-connect/lambdas/python/common/requirements.txt b/backend/compact-connect/lambdas/python/common/requirements.txt index 828f0ab9d..ce0d0029d 100644 --- a/backend/compact-connect/lambdas/python/common/requirements.txt +++ b/backend/compact-connect/lambdas/python/common/requirements.txt @@ -1,32 +1,32 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/common/requirements.in +# pip-compile --no-emit-index-url --no-strip-extras lambdas/python/common/requirements.in # argon2-cffi==25.1.0 # via -r lambdas/python/common/requirements.in argon2-cffi-bindings==25.1.0 # via argon2-cffi -aws-lambda-powertools==3.20.0 +aws-lambda-powertools==3.22.0 # via -r lambdas/python/common/requirements.in -boto3==1.40.35 +boto3==1.40.56 # via -r lambdas/python/common/requirements.in -botocore==1.40.35 +botocore==1.40.56 # via # boto3 # s3transfer -certifi==2025.8.3 +certifi==2025.10.5 # via requests cffi==2.0.0 # via # argon2-cffi-bindings # cryptography -charset-normalizer==3.4.3 +charset-normalizer==3.4.4 # via requests -cryptography==46.0.1 +cryptography==46.0.3 # via -r lambdas/python/common/requirements.in -idna==3.10 +idna==3.11 # via requests jmespath==1.0.1 # via 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 71f7ece0b..1599702ce 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 @@ -1,7 +1,7 @@ import json from datetime import UTC, date, datetime from unittest.mock import patch -from uuid import uuid4 +from uuid import UUID, uuid4 from boto3.dynamodb.conditions import Key from cc_common.exceptions import CCAwsServiceException, CCInvalidRequestException @@ -16,6 +16,10 @@ class TestDataClient(TstFunction): sample_privilege_attestations = [{'attestationId': 'jurisprudence-confirmation', 'version': '1'}] + def setUp(self): + super().setUp() + self.maxDiff = None + def test_get_provider(self): from cc_common.data_model.data_client import DataClient @@ -61,10 +65,10 @@ def test_get_provider_garbage_in_db(self): for item in resp['items']: self.assertNotIn('something_unexpected', item) - def _load_provider_data(self) -> str: + def _load_provider_data(self) -> UUID: with open('tests/resources/dynamo/provider.json') as f: provider_record = json.load(f) - provider_id = provider_record['providerId'] + provider_id = UUID(provider_record['providerId']) provider_record['privilegeJurisdictions'] = set(provider_record['privilegeJurisdictions']) self._provider_table.put_item(Item=provider_record) @@ -725,7 +729,7 @@ def test_deactivate_privilege_updates_record(self): 'pk': f'aslp#PROVIDER#{provider_id}', 'sk': 'aslp#PROVIDER#privilege/ne/aud#', 'type': 'privilege', - 'providerId': provider_id, + 'providerId': str(provider_id), 'compact': 'aslp', 'licenseJurisdiction': 'oh', 'licenseType': 'audiologist', @@ -769,7 +773,7 @@ def test_deactivate_privilege_updates_record(self): 'pk': f'aslp#PROVIDER#{provider_id}', 'sk': 'aslp#PROVIDER#privilege/ne/aud#', 'type': 'privilege', - 'providerId': provider_id, + 'providerId': str(provider_id), 'compact': 'aslp', 'licenseJurisdiction': 'oh', 'licenseType': 'audiologist', @@ -790,7 +794,7 @@ def test_deactivate_privilege_updates_record(self): 'sk': 'aslp#PROVIDER#privilege/ne/aud#UPDATE#1731110399/aac682a76e1182a641a1b40dd606ae51', 'type': 'privilegeUpdate', 'updateType': 'deactivation', - 'providerId': provider_id, + 'providerId': str(provider_id), 'compact': 'aslp', 'compactTransactionIdGSIPK': 'COMPACT#aslp#TX#1234567890#', 'jurisdiction': 'ne', @@ -866,7 +870,7 @@ def test_deactivate_privilege_on_inactive_privilege_raises_exception(self): 'pk': f'aslp#PROVIDER#{provider_id}', 'sk': 'aslp#PROVIDER#privilege/ne/aud#', 'type': 'privilege', - 'providerId': provider_id, + 'providerId': str(provider_id), 'compact': 'aslp', 'jurisdiction': 'ne', 'licenseJurisdiction': 'oh', @@ -888,7 +892,7 @@ def test_deactivate_privilege_on_inactive_privilege_raises_exception(self): 'sk': 'aslp#PROVIDER#privilege/ne/aud#UPDATE#1731110399/483bebc6cb3fd6b517f8ce9ad706c518', 'type': 'privilegeUpdate', 'updateType': 'renewal', - 'providerId': provider_id, + 'providerId': str(provider_id), 'compact': 'aslp', 'compactTransactionIdGSIPK': 'COMPACT#aslp#TX#1234567890#', 'jurisdiction': 'ne', @@ -1028,3 +1032,887 @@ def mock_query(**kwargs): finally: # Restore the original query method self.config.provider_table.query = original_query + + def test_create_privilege_investigation_success(self): + """Test successful creation of privilege investigation""" + from cc_common.data_model.data_client import DataClient + + # Load test data + provider_id = self._load_provider_data() + + client = DataClient(self.config) + + # Create investigation data using test data generator + investigation = self.test_data_generator.generate_default_investigation( + { + 'providerId': provider_id, + 'compact': 'aslp', + 'jurisdiction': 'ne', + 'licenseType': 'speech-language pathologist', + 'investigationAgainst': 'privilege', + } + ) + + # Call the method + client.create_investigation(investigation) + + # Verify investigation record was created + investigation_records = self.config.provider_table.query( + KeyConditionExpression=Key('pk').eq(f'aslp#PROVIDER#{provider_id}') + & Key('sk').begins_with('aslp#PROVIDER#privilege/ne/slp#INVESTIGATION#') + )['Items'] + + self.assertEqual(1, len(investigation_records)) + investigation_record = investigation_records[0] + + # Verify the complete investigation record structure + expected_investigation = { + 'pk': f'aslp#PROVIDER#{provider_id}', + 'sk': f'aslp#PROVIDER#privilege/ne/slp#INVESTIGATION#{investigation.investigationId}', + 'type': 'investigation', + 'compact': 'aslp', + 'providerId': str(provider_id), + 'jurisdiction': 'ne', + 'licenseType': 'speech-language pathologist', + 'investigationAgainst': 'privilege', + 'investigationId': str(investigation.investigationId), + 'submittingUser': str(investigation.submittingUser), + 'creationDate': investigation.creationDate.isoformat(), + } + # Pop dynamic fields that we don't want to assert on + investigation_record.pop('dateOfUpdate') + + self.assertEqual(expected_investigation, investigation_record) + + # Verify privilege record was updated with investigation status + privilege_records = self.config.provider_table.query( + KeyConditionExpression=Key('pk').eq(f'aslp#PROVIDER#{provider_id}') + & Key('sk').eq('aslp#PROVIDER#privilege/ne/slp#') + )['Items'] + + self.assertEqual(1, len(privilege_records)) + privilege_record = privilege_records[0] + self.assertEqual('underInvestigation', privilege_record['investigationStatus']) + + # Verify update record was created + update_records = self.config.provider_table.query( + KeyConditionExpression=Key('pk').eq(f'aslp#PROVIDER#{provider_id}') + & Key('sk').begins_with('aslp#PROVIDER#privilege/ne/slp#UPDATE#') + )['Items'] + + self.assertEqual(1, len(update_records)) + update_record = update_records[0] + + # Verify the complete update record structure + expected_update = { + 'pk': f'aslp#PROVIDER#{provider_id}', + 'compactTransactionIdGSIPK': 'COMPACT#aslp#TX#1234567890#', + 'type': 'privilegeUpdate', + 'updateType': 'investigation', + 'compact': 'aslp', + 'providerId': str(provider_id), + 'jurisdiction': 'ne', + 'licenseType': 'speech-language pathologist', + 'createDate': investigation.creationDate.isoformat(), + 'effectiveDate': investigation.creationDate.isoformat(), + 'previous': { + 'administratorSetStatus': 'active', + 'attestations': [{'attestationId': 'jurisprudence-confirmation', 'version': '1'}], + 'compactTransactionId': '1234567890', + 'dateOfExpiration': '2025-04-04', + 'dateOfIssuance': '2016-05-05T12:59:59+00:00', + 'dateOfRenewal': '2020-05-05T12:59:59+00:00', + 'dateOfUpdate': '2020-05-05T12:59:59+00:00', + 'licenseJurisdiction': 'oh', + 'privilegeId': 'SLP-NE-1', + }, + 'updatedValues': { + 'investigationStatus': 'underInvestigation', + }, + 'investigationDetails': { + 'investigationId': str(investigation.investigationId), + }, + } + # Pop dynamic fields that we don't want to assert on + update_record.pop('dateOfUpdate') + update_record.pop('sk') + + self.assertEqual(expected_update, update_record) + + def test_create_license_investigation_success(self): + """Test successful creation of license investigation""" + from cc_common.data_model.data_client import DataClient + from cc_common.data_model.schema.investigation import InvestigationData + + # Load test data + provider_id = self._load_provider_data() + + client = DataClient(self.config) + + # Create investigation data + investigation = InvestigationData.create_new( + { + 'providerId': provider_id, + 'compact': 'aslp', + 'jurisdiction': 'oh', + 'licenseTypeAbbreviation': 'slp', + 'licenseType': 'speech-language pathologist', + 'investigationAgainst': 'license', + 'submittingUser': str(uuid4()), + 'creationDate': datetime.fromisoformat('2024-11-08T23:59:59+00:00'), + 'investigationId': str(uuid4()), + } + ) + + # Call the method + client.create_investigation(investigation) + + # Verify investigation record was created + investigation_records = self.config.provider_table.query( + KeyConditionExpression=Key('pk').eq(f'aslp#PROVIDER#{provider_id}') + & Key('sk').begins_with('aslp#PROVIDER#license/oh/slp#INVESTIGATION#') + )['Items'] + + self.assertEqual(1, len(investigation_records)) + investigation_record = investigation_records[0] + + # Verify the complete investigation record structure + expected_investigation = { + 'pk': f'aslp#PROVIDER#{provider_id}', + 'sk': f'aslp#PROVIDER#license/oh/slp#INVESTIGATION#{investigation.investigationId}', + 'type': 'investigation', + 'compact': 'aslp', + 'providerId': str(provider_id), + 'jurisdiction': 'oh', + 'licenseType': 'speech-language pathologist', + 'investigationAgainst': 'license', + 'investigationId': str(investigation.investigationId), + 'submittingUser': str(investigation.submittingUser), + 'creationDate': investigation.creationDate.isoformat(), + } + # Pop dynamic fields that we don't want to assert on + investigation_record.pop('dateOfUpdate') + + self.assertEqual(expected_investigation, investigation_record) + + # Verify license record was updated with investigation status + license_records = self.config.provider_table.query( + KeyConditionExpression=Key('pk').eq(f'aslp#PROVIDER#{provider_id}') + & Key('sk').eq('aslp#PROVIDER#license/oh/slp#') + )['Items'] + + self.assertEqual(1, len(license_records)) + license_record = license_records[0] + self.assertEqual('underInvestigation', license_record['investigationStatus']) + + # Verify update record was created + update_records = self.config.provider_table.query( + KeyConditionExpression=Key('pk').eq(f'aslp#PROVIDER#{provider_id}') + & Key('sk').begins_with('aslp#PROVIDER#license/oh/slp#UPDATE#') + )['Items'] + + self.assertEqual(1, len(update_records)) + update_record = update_records[0] + + # Verify the complete update record structure + expected_update = { + 'pk': f'aslp#PROVIDER#{provider_id}', + 'type': 'licenseUpdate', + 'updateType': 'investigation', + 'compact': 'aslp', + 'providerId': str(provider_id), + 'jurisdiction': 'oh', + 'licenseType': 'speech-language pathologist', + 'createDate': investigation.creationDate.isoformat(), + 'effectiveDate': investigation.creationDate.isoformat(), + 'previous': { + 'npi': '0608337260', + 'licenseNumber': 'A0608337260', + 'ssnLastFour': '1234', + 'givenName': 'Björk', + 'middleName': 'Gunnar', + 'familyName': 'Guðmundsdóttir', + 'dateOfUpdate': '2024-06-06T12:59:59+00:00', + 'dateOfIssuance': '2010-06-06', + 'dateOfRenewal': '2020-04-04', + 'dateOfExpiration': '2025-04-04', + 'dateOfBirth': '1985-06-06', + 'homeAddressStreet1': '123 A St.', + 'homeAddressStreet2': 'Apt 321', + 'homeAddressCity': 'Columbus', + 'homeAddressState': 'oh', + 'homeAddressPostalCode': '43004', + 'emailAddress': 'björk@example.com', + 'phoneNumber': '+13213214321', + 'licenseStatusName': 'DEFINITELY_A_HUMAN', + 'jurisdictionUploadedLicenseStatus': 'active', + 'jurisdictionUploadedCompactEligibility': 'eligible', + }, + 'updatedValues': { + 'investigationStatus': 'underInvestigation', + }, + 'investigationDetails': { + 'investigationId': str(investigation.investigationId), + }, + } + # Pop dynamic fields that we don't want to assert on + update_record.pop('dateOfUpdate') + update_record.pop('sk') + + self.assertEqual(expected_update, update_record) + + def test_create_privilege_investigation_privilege_not_found(self): + """Test creation of privilege investigation when privilege doesn't exist""" + from cc_common.data_model.data_client import DataClient + from cc_common.data_model.schema.investigation import InvestigationData + from cc_common.exceptions import CCNotFoundException + + # Load test data, privilege in Nebraska, license in Ohio + provider_id = self._load_provider_data() + + client = DataClient(self.config) + + # Create investigation data for non-existent privilege (no privilege in Ohio) + investigation = InvestigationData.create_new( + { + 'providerId': str(provider_id), + 'compact': 'aslp', + 'jurisdiction': 'oh', + 'licenseTypeAbbreviation': 'slp', + 'licenseType': 'speech-language pathologist', + 'investigationAgainst': 'privilege', + 'submittingUser': str(uuid4()), + 'creationDate': datetime.fromisoformat('2024-11-08T23:59:59+00:00'), + 'investigationId': str(uuid4()), + } + ) + + # Call the method and expect exception + with self.assertRaises(CCNotFoundException) as context: + client.create_investigation(investigation) + + self.assertIn('Privilege not found', str(context.exception)) + + def test_create_license_investigation_license_not_found(self): + """Test creation of license investigation when license doesn't exist""" + from cc_common.data_model.data_client import DataClient + from cc_common.data_model.schema.investigation import InvestigationData + from cc_common.exceptions import CCNotFoundException + + # Load test data, privilege in Nebraska, license in Ohio + provider_id = self._load_provider_data() + + client = DataClient(self.config) + + # Create investigation data for non-existent license (no license in Nebraska) + investigation = InvestigationData.create_new( + { + 'providerId': str(provider_id), + 'compact': 'aslp', + 'jurisdiction': 'ne', + 'licenseTypeAbbreviation': 'slp', + 'licenseType': 'speech-language pathologist', + 'investigationAgainst': 'license', + 'submittingUser': str(uuid4()), + 'creationDate': datetime.fromisoformat('2024-11-08T23:59:59+00:00'), + 'investigationId': str(uuid4()), + } + ) + + # Call the method and expect exception + with self.assertRaises(CCNotFoundException) as context: + client.create_investigation(investigation) + + self.assertIn('License not found', str(context.exception)) + + def test_close_privilege_investigation_success(self): + """Test successful closing of privilege investigation""" + from cc_common.data_model.data_client import DataClient + from cc_common.data_model.schema.common import InvestigationAgainstEnum + from cc_common.data_model.schema.investigation import InvestigationData + + # Load test data + provider_id = self._load_provider_data() + + client = DataClient(self.config) + + # First create an investigation + investigation = InvestigationData.create_new( + { + 'providerId': provider_id, + 'compact': 'aslp', + 'jurisdiction': 'ne', + 'licenseTypeAbbreviation': 'slp', + 'licenseType': 'speech-language pathologist', + 'investigationAgainst': 'privilege', + 'submittingUser': str(uuid4()), + 'creationDate': datetime.fromisoformat('2024-11-08T23:59:59+00:00'), + 'investigationId': uuid4(), + } + ) + + client.create_investigation(investigation) + + # Now close the investigation + closing_user = str(uuid4()) + client.close_investigation( + compact='aslp', + provider_id=provider_id, + jurisdiction='ne', + license_type_abbreviation='slp', + investigation_id=investigation.investigationId, + closing_user=closing_user, + close_date=datetime.fromisoformat('2024-11-08T23:59:59+00:00'), + investigation_against=InvestigationAgainstEnum.PRIVILEGE, + ) + + # Verify investigation record was updated with close information + investigation_records = self.config.provider_table.query( + KeyConditionExpression=Key('pk').eq(f'aslp#PROVIDER#{provider_id}') + & Key('sk').begins_with('aslp#PROVIDER#privilege/ne/slp#INVESTIGATION#') + )['Items'] + + self.assertEqual(1, len(investigation_records)) + investigation_record = investigation_records[0] + + # Verify the investigation record was updated with close information + expected_investigation_close = { + 'pk': f'aslp#PROVIDER#{provider_id}', + 'sk': f'aslp#PROVIDER#privilege/ne/slp#INVESTIGATION#{investigation.investigationId}', + 'type': 'investigation', + 'compact': 'aslp', + 'providerId': str(provider_id), + 'jurisdiction': 'ne', + 'licenseType': 'speech-language pathologist', + 'investigationAgainst': 'privilege', + 'investigationId': str(investigation.investigationId), + 'submittingUser': str(investigation.submittingUser), + 'creationDate': investigation.creationDate.isoformat(), + 'closeDate': investigation.creationDate.isoformat(), + 'closingUser': closing_user, + } + # Pop dynamic fields that we don't want to assert on + investigation_record.pop('dateOfUpdate') + + self.assertEqual(expected_investigation_close, investigation_record) + + # Verify privilege record no longer has investigation status + privilege_records = self.config.provider_table.query( + KeyConditionExpression=Key('pk').eq(f'aslp#PROVIDER#{provider_id}') + & Key('sk').eq('aslp#PROVIDER#privilege/ne/slp#') + )['Items'] + + self.assertEqual(1, len(privilege_records)) + privilege_record = privilege_records[0] + self.assertNotIn('investigationStatus', privilege_record) + + # Verify update record was created for closure + update_records = self.config.provider_table.query( + KeyConditionExpression=Key('pk').eq(f'aslp#PROVIDER#{provider_id}') + & Key('sk').begins_with('aslp#PROVIDER#privilege/ne/slp#UPDATE#') + )['Items'] + + # Should have 2 update records: one for creation, one for closure + self.assertEqual(2, len(update_records)) + + # Find the closure update record + closure_update = None + for update_record in update_records: + if update_record.get('updateType') == 'closingInvestigation': + closure_update = update_record + break + + self.assertIsNotNone(closure_update, 'Closure update record not found!') + + # Verify the complete closure update record structure + expected_closure_update = { + 'pk': f'aslp#PROVIDER#{provider_id}', + 'type': 'privilegeUpdate', + 'updateType': 'closingInvestigation', + 'compact': 'aslp', + 'providerId': str(provider_id), + 'jurisdiction': 'ne', + 'licenseType': 'speech-language pathologist', + 'createDate': investigation.creationDate.isoformat(), + 'effectiveDate': investigation.creationDate.isoformat(), + 'previous': { + 'administratorSetStatus': 'active', + 'attestations': [{'attestationId': 'jurisprudence-confirmation', 'version': '1'}], + 'compactTransactionId': '1234567890', + 'dateOfExpiration': '2025-04-04', + 'dateOfIssuance': '2016-05-05T12:59:59+00:00', + 'dateOfRenewal': '2020-05-05T12:59:59+00:00', + 'dateOfUpdate': '2024-11-08T23:59:59+00:00', + 'licenseJurisdiction': 'oh', + 'privilegeId': 'SLP-NE-1', + 'investigationStatus': 'underInvestigation', + }, + 'updatedValues': {}, + 'removedValues': ['investigationStatus'], + } + # Pop dynamic fields that we don't want to assert on + closure_update.pop('dateOfUpdate') + closure_update.pop('sk') + # Only pop compactTransactionIdGSIPK if it exists + if 'compactTransactionIdGSIPK' in closure_update: + closure_update.pop('compactTransactionIdGSIPK') + + self.assertEqual(expected_closure_update, closure_update) + + def test_close_license_investigation_success(self): + """Test successful closing of license investigation""" + from cc_common.data_model.data_client import DataClient + from cc_common.data_model.schema.common import InvestigationAgainstEnum + from cc_common.data_model.schema.investigation import InvestigationData + + # Load test data + provider_id = self._load_provider_data() + + client = DataClient(self.config) + + # First create an investigation + investigation = InvestigationData.create_new( + { + 'providerId': provider_id, + 'compact': 'aslp', + 'jurisdiction': 'oh', + 'licenseTypeAbbreviation': 'slp', + 'licenseType': 'speech-language pathologist', + 'investigationAgainst': 'license', + 'submittingUser': str(uuid4()), + 'creationDate': datetime.fromisoformat('2024-11-08T23:59:59+00:00'), + 'investigationId': str(uuid4()), + } + ) + + client.create_investigation(investigation) + + # Now close the investigation + closing_user = str(uuid4()) + close_date = datetime.fromisoformat('2024-11-08T23:59:59+00:00') + client.close_investigation( + compact='aslp', + provider_id=provider_id, + jurisdiction='oh', + license_type_abbreviation='slp', + investigation_id=investigation.investigationId, + closing_user=closing_user, + close_date=close_date, + investigation_against=InvestigationAgainstEnum.LICENSE, + ) + + # Verify investigation record was updated with close information + investigation_records = self.config.provider_table.query( + KeyConditionExpression=Key('pk').eq(f'aslp#PROVIDER#{provider_id}') + & Key('sk').begins_with('aslp#PROVIDER#license/oh/slp#INVESTIGATION#') + )['Items'] + + self.assertEqual(1, len(investigation_records)) + investigation_record = investigation_records[0] + + # Verify the investigation record was updated with close information + expected_investigation_close = { + 'pk': f'aslp#PROVIDER#{provider_id}', + 'sk': f'aslp#PROVIDER#license/oh/slp#INVESTIGATION#{investigation.investigationId}', + 'type': 'investigation', + 'compact': 'aslp', + 'providerId': str(provider_id), + 'jurisdiction': 'oh', + 'licenseType': 'speech-language pathologist', + 'investigationAgainst': 'license', + 'investigationId': str(investigation.investigationId), + 'submittingUser': str(investigation.submittingUser), + 'creationDate': investigation.creationDate.isoformat(), + 'closeDate': close_date.isoformat(), + 'closingUser': closing_user, + } + # Pop dynamic fields that we don't want to assert on + investigation_record.pop('dateOfUpdate') + + self.assertEqual(expected_investigation_close, investigation_record) + + # Verify license record no longer has investigation status + license_records = self.config.provider_table.query( + KeyConditionExpression=Key('pk').eq(f'aslp#PROVIDER#{provider_id}') + & Key('sk').eq('aslp#PROVIDER#license/oh/slp#') + )['Items'] + + self.assertEqual(1, len(license_records)) + license_record = license_records[0] + self.assertNotIn('investigationStatus', license_record) + + # Verify update record was created for closure + update_records = self.config.provider_table.query( + KeyConditionExpression=Key('pk').eq(f'aslp#PROVIDER#{provider_id}') + & Key('sk').begins_with('aslp#PROVIDER#license/oh/slp#UPDATE#') + )['Items'] + + # Should have 2 update records: one for creation, one for closure + self.assertEqual(2, len(update_records)) + + # Find the closure update record + closure_update = None + for update_record in update_records: + if update_record.get('updateType') == 'closingInvestigation': + closure_update = update_record + break + + self.assertIsNotNone(closure_update, 'Closure update not found!') + + # Verify the complete closure update record structure + expected_closure_update = { + 'pk': f'aslp#PROVIDER#{provider_id}', + 'type': 'licenseUpdate', + 'updateType': 'closingInvestigation', + 'compact': 'aslp', + 'providerId': str(provider_id), + 'jurisdiction': 'oh', + 'licenseType': 'speech-language pathologist', + 'createDate': investigation.creationDate.isoformat(), + 'effectiveDate': investigation.creationDate.isoformat(), + 'previous': { + 'npi': '0608337260', + 'licenseNumber': 'A0608337260', + 'ssnLastFour': '1234', + 'givenName': 'Björk', + 'middleName': 'Gunnar', + 'familyName': 'Guðmundsdóttir', + 'dateOfUpdate': '2024-11-08T23:59:59+00:00', + 'dateOfIssuance': '2010-06-06', + 'dateOfRenewal': '2020-04-04', + 'dateOfExpiration': '2025-04-04', + 'dateOfBirth': '1985-06-06', + 'homeAddressStreet1': '123 A St.', + 'homeAddressStreet2': 'Apt 321', + 'homeAddressCity': 'Columbus', + 'homeAddressState': 'oh', + 'homeAddressPostalCode': '43004', + 'emailAddress': 'björk@example.com', + 'phoneNumber': '+13213214321', + 'licenseStatusName': 'DEFINITELY_A_HUMAN', + 'jurisdictionUploadedLicenseStatus': 'active', + 'jurisdictionUploadedCompactEligibility': 'eligible', + 'investigationStatus': 'underInvestigation', + }, + 'updatedValues': {}, + 'removedValues': ['investigationStatus'], + } + # Pop dynamic fields that we don't want to assert on + closure_update.pop('dateOfUpdate') + closure_update.pop('sk') + + self.assertEqual(expected_closure_update, closure_update) + + def test_close_privilege_investigation_not_found(self): + """Test closing privilege investigation when investigation doesn't exist""" + from cc_common.data_model.data_client import DataClient + from cc_common.data_model.schema.common import InvestigationAgainstEnum + from cc_common.exceptions import CCNotFoundException + + # Load test data + provider_id = self._load_provider_data() + + client = DataClient(self.config) + + # Try to close a non-existent investigation + with self.assertRaises(CCNotFoundException) as context: + client.close_investigation( + compact='aslp', + provider_id=provider_id, + jurisdiction='ne', + license_type_abbreviation='slp', + investigation_id=uuid4(), + closing_user=str(uuid4()), + close_date=datetime.fromisoformat('2024-11-08T23:59:59+00:00'), + investigation_against=InvestigationAgainstEnum.PRIVILEGE, + ) + + self.assertIn('Investigation not found', str(context.exception)) + + def test_close_license_investigation_not_found(self): + """Test closing license investigation when investigation doesn't exist""" + from cc_common.data_model.data_client import DataClient + from cc_common.data_model.schema.common import InvestigationAgainstEnum + from cc_common.exceptions import CCNotFoundException + + # Load test data + provider_id = self._load_provider_data() + + client = DataClient(self.config) + + # Try to close a non-existent investigation + with self.assertRaises(CCNotFoundException) as context: + client.close_investigation( + compact='aslp', + provider_id=provider_id, + jurisdiction='oh', + license_type_abbreviation='slp', + investigation_id=uuid4(), + closing_user=str(uuid4()), + close_date=datetime.fromisoformat('2024-11-08T23:59:59+00:00'), + investigation_against=InvestigationAgainstEnum.LICENSE, + ) + + self.assertIn('Investigation not found', str(context.exception)) + + def test_close_privilege_investigation_already_closed(self): + """Test closing privilege investigation when investigation was already closed""" + from cc_common.data_model.data_client import DataClient + from cc_common.data_model.schema.common import InvestigationAgainstEnum + from cc_common.data_model.schema.investigation import InvestigationData + from cc_common.exceptions import CCNotFoundException + + # Load test data + provider_id = self._load_provider_data() + + client = DataClient(self.config) + + # First create an investigation + investigation = InvestigationData.create_new( + { + 'providerId': provider_id, + 'compact': 'aslp', + 'jurisdiction': 'ne', + 'licenseTypeAbbreviation': 'slp', + 'licenseType': 'speech-language pathologist', + 'investigationAgainst': 'privilege', + 'submittingUser': str(uuid4()), + 'creationDate': datetime.fromisoformat('2024-11-08T23:59:59+00:00'), + 'investigationId': uuid4(), + } + ) + + client.create_investigation(investigation) + + # Now close the investigation + closing_user = str(uuid4()) + client.close_investigation( + compact='aslp', + provider_id=provider_id, + jurisdiction='ne', + license_type_abbreviation='slp', + investigation_id=investigation.investigationId, + closing_user=closing_user, + close_date=datetime.fromisoformat('2024-11-08T23:59:59+00:00'), + investigation_against=InvestigationAgainstEnum.PRIVILEGE, + ) + with self.assertRaises(CCNotFoundException) as context: + client.close_investigation( + compact='aslp', + provider_id=provider_id, + jurisdiction='ne', + license_type_abbreviation='slp', + investigation_id=investigation.investigationId, + closing_user=closing_user, + close_date=datetime.fromisoformat('2024-11-08T23:59:59+00:00'), + investigation_against=InvestigationAgainstEnum.PRIVILEGE, + ) + + self.assertIn('Investigation not found', str(context.exception)) + + def test_close_license_investigation_already_closed(self): + """Test closing license investigation when investigation was already closed""" + from cc_common.data_model.data_client import DataClient + from cc_common.data_model.schema.common import InvestigationAgainstEnum + from cc_common.data_model.schema.investigation import InvestigationData + from cc_common.exceptions import CCNotFoundException + + # Load test data + provider_id = self._load_provider_data() + + client = DataClient(self.config) + + # First create an investigation + investigation = InvestigationData.create_new( + { + 'providerId': provider_id, + 'compact': 'aslp', + 'jurisdiction': 'oh', + 'licenseTypeAbbreviation': 'slp', + 'licenseType': 'speech-language pathologist', + 'investigationAgainst': 'license', + 'submittingUser': str(uuid4()), + 'creationDate': datetime.fromisoformat('2024-11-08T23:59:59+00:00'), + 'investigationId': uuid4(), + } + ) + + client.create_investigation(investigation) + + # Now close the investigation + closing_user = str(uuid4()) + client.close_investigation( + compact='aslp', + provider_id=provider_id, + jurisdiction='oh', + license_type_abbreviation='slp', + investigation_id=investigation.investigationId, + closing_user=closing_user, + close_date=datetime.fromisoformat('2024-11-08T23:59:59+00:00'), + investigation_against=InvestigationAgainstEnum.LICENSE, + ) + with self.assertRaises(CCNotFoundException) as context: + client.close_investigation( + compact='aslp', + provider_id=provider_id, + jurisdiction='oh', + license_type_abbreviation='slp', + investigation_id=investigation.investigationId, + closing_user=closing_user, + close_date=datetime.fromisoformat('2024-11-08T23:59:59+00:00'), + investigation_against=InvestigationAgainstEnum.LICENSE, + ) + + self.assertIn('Investigation not found', str(context.exception)) + + def test_close_privilege_investigation_with_encumbrance(self): + """Test closing privilege investigation with encumbrance creation""" + from cc_common.data_model.data_client import DataClient + from cc_common.data_model.schema.common import InvestigationAgainstEnum + from cc_common.data_model.schema.investigation import InvestigationData + + # Load test data + provider_id = self._load_provider_data() + + client = DataClient(self.config) + + # First create an investigation + investigation = InvestigationData.create_new( + { + 'providerId': provider_id, + 'compact': 'aslp', + 'jurisdiction': 'ne', + 'licenseTypeAbbreviation': 'slp', + 'licenseType': 'speech-language pathologist', + 'investigationAgainst': 'privilege', + 'submittingUser': str(uuid4()), + 'creationDate': datetime.fromisoformat('2024-11-08T23:59:59+00:00'), + 'investigationId': uuid4(), + } + ) + + client.create_investigation(investigation) + + # Now close the investigation with encumbrance creation + closing_user = str(uuid4()) + resulting_encumbrance_id = uuid4() + + close_date = datetime.fromisoformat('2024-11-08T23:59:59+00:00') + client.close_investigation( + compact='aslp', + provider_id=provider_id, + jurisdiction='ne', + license_type_abbreviation='slp', + investigation_id=investigation.investigationId, + closing_user=closing_user, + close_date=datetime.fromisoformat('2024-11-08T23:59:59+00:00'), + investigation_against=InvestigationAgainstEnum.PRIVILEGE, + resulting_encumbrance_id=resulting_encumbrance_id, + ) + + # Verify investigation record was updated with close information and encumbrance reference + investigation_records = self.config.provider_table.query( + KeyConditionExpression=Key('pk').eq(f'aslp#PROVIDER#{provider_id}') + & Key('sk').begins_with('aslp#PROVIDER#privilege/ne/slp#INVESTIGATION#') + )['Items'] + + self.assertEqual(1, len(investigation_records)) + investigation_record = investigation_records[0] + + # Verify the investigation record was updated with close information and encumbrance reference + expected_investigation_close = { + 'pk': f'aslp#PROVIDER#{provider_id}', + 'sk': f'aslp#PROVIDER#privilege/ne/slp#INVESTIGATION#{investigation.investigationId}', + 'type': 'investigation', + 'compact': 'aslp', + 'providerId': str(provider_id), + 'jurisdiction': 'ne', + 'licenseType': 'speech-language pathologist', + 'investigationAgainst': 'privilege', + 'investigationId': str(investigation.investigationId), + 'submittingUser': str(investigation.submittingUser), + 'creationDate': investigation.creationDate.isoformat(), + 'closeDate': close_date.isoformat(), + 'closingUser': closing_user, + 'resultingEncumbranceId': str(resulting_encumbrance_id), + } + # Pop dynamic fields that we don't want to assert on + investigation_record.pop('dateOfUpdate') + + self.assertEqual(expected_investigation_close, investigation_record) + + def test_close_license_investigation_with_encumbrance(self): + """Test closing license investigation with encumbrance creation""" + from cc_common.data_model.data_client import DataClient + from cc_common.data_model.schema.common import InvestigationAgainstEnum + from cc_common.data_model.schema.investigation import InvestigationData + + # Load test data + provider_id = self._load_provider_data() + + client = DataClient(self.config) + + # First create an investigation + investigation = InvestigationData.create_new( + { + 'providerId': provider_id, + 'compact': 'aslp', + 'jurisdiction': 'oh', + 'licenseTypeAbbreviation': 'slp', + 'licenseType': 'speech-language pathologist', + 'investigationAgainst': 'license', + 'submittingUser': str(uuid4()), + 'creationDate': datetime.fromisoformat('2024-11-08T23:59:59+00:00'), + 'investigationId': uuid4(), + } + ) + + client.create_investigation(investigation) + + # Now close the investigation with encumbrance creation + closing_user = str(uuid4()) + resulting_encumbrance_id = uuid4() + + close_date = datetime.fromisoformat('2024-11-08T23:59:59+00:00') + client.close_investigation( + compact='aslp', + provider_id=provider_id, + jurisdiction='oh', + license_type_abbreviation='slp', + investigation_id=investigation.investigationId, + closing_user=closing_user, + close_date=datetime.fromisoformat('2024-11-08T23:59:59+00:00'), + investigation_against=InvestigationAgainstEnum.LICENSE, + resulting_encumbrance_id=resulting_encumbrance_id, + ) + + # Verify investigation record was updated with close information and encumbrance reference + investigation_records = self.config.provider_table.query( + KeyConditionExpression=Key('pk').eq(f'aslp#PROVIDER#{provider_id}') + & Key('sk').begins_with('aslp#PROVIDER#license/oh/slp#INVESTIGATION#') + )['Items'] + + self.assertEqual(1, len(investigation_records)) + investigation_record = investigation_records[0] + + # Verify the investigation record was updated with close information and encumbrance reference + expected_investigation_close = { + 'pk': f'aslp#PROVIDER#{provider_id}', + 'sk': f'aslp#PROVIDER#license/oh/slp#INVESTIGATION#{investigation.investigationId}', + 'type': 'investigation', + 'compact': 'aslp', + 'providerId': str(provider_id), + 'jurisdiction': 'oh', + 'licenseType': 'speech-language pathologist', + 'investigationAgainst': 'license', + 'investigationId': str(investigation.investigationId), + 'submittingUser': str(investigation.submittingUser), + 'creationDate': investigation.creationDate.isoformat(), + 'closeDate': close_date.isoformat(), + 'closingUser': closing_user, + 'resultingEncumbranceId': str(resulting_encumbrance_id), + } + # Pop dynamic fields that we don't want to assert on + investigation_record.pop('dateOfUpdate') + + self.assertEqual(expected_investigation_close, investigation_record) 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 8ee8ebbae..47ab5f47d 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 @@ -49,7 +49,8 @@ "homeAddressPostalCode": "43004", "emailAddress": "björk@example.com", "phoneNumber": "+13213214321", - "adverseActions": [] + "adverseActions": [], + "investigations": [] } ], "privileges": [ @@ -70,6 +71,7 @@ "privilegeId": "SLP-NE-1", "administratorSetStatus": "active", "adverseActions": [], + "investigations": [], "attestations": [ { "attestationId": "jurisprudence-confirmation", diff --git a/backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_investigation.py b/backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_investigation.py new file mode 100644 index 000000000..4278fd206 --- /dev/null +++ b/backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_investigation.py @@ -0,0 +1,161 @@ +from marshmallow import ValidationError + +from tests import TstLambdas + + +class TestInvestigationRecordSchema(TstLambdas): + def setUp(self): + from common_test.test_data_generator import TestDataGenerator + + self.test_data_generator = TestDataGenerator + + def test_serde(self): + """Test round-trip deserialization/serialization""" + from cc_common.data_model.schema.investigation.record import InvestigationRecordSchema + + expected_investigation = ( + self.test_data_generator.generate_default_investigation().serialize_to_database_record() + ) + + schema = InvestigationRecordSchema() + loaded_schema = schema.load(expected_investigation.copy()) + + investigation_data = schema.dump(loaded_schema) + + # Pop dynamic fields + expected_investigation.pop('dateOfUpdate') + investigation_data.pop('dateOfUpdate') + + self.assertEqual(expected_investigation, investigation_data) + + def test_invalid(self): + from cc_common.data_model.schema.investigation.record import InvestigationRecordSchema + + investigation_data = self.test_data_generator.generate_default_investigation().to_dict() + investigation_data.pop('providerId') + + with self.assertRaises(ValidationError): + InvestigationRecordSchema().load(investigation_data) + + def test_invalid_investigation_against(self): + from cc_common.data_model.schema.common import CompactEligibilityStatus + from cc_common.data_model.schema.investigation import InvestigationData + + investigation_data = self.test_data_generator.generate_default_investigation() + + # setting to an invalid value from another enum + investigation_data.investigationAgainst = CompactEligibilityStatus.ELIGIBLE + + with self.assertRaises(ValidationError): + InvestigationData.from_database_record(investigation_data.serialize_to_database_record()) + + def test_invalid_license_type(self): + from cc_common.data_model.schema.investigation import InvestigationData + + investigation_data = self.test_data_generator.generate_default_investigation() + + # setting to an invalid license type name + investigation_data.licenseType = 'foobar' + + with self.assertRaises(ValidationError): + InvestigationData.from_database_record(investigation_data.serialize_to_database_record()) + + +class TestInvestigationDataClass(TstLambdas): + def setUp(self): + from common_test.test_data_generator import TestDataGenerator + + self.test_data_generator = TestDataGenerator + + def test_investigation_data_class_getters_return_expected_values(self): + from cc_common.data_model.schema.investigation import InvestigationData + + investigation_data = self.test_data_generator.generate_default_investigation() + + investigation = InvestigationData.from_database_record(investigation_data.serialize_to_database_record()) + + # Use to_dict() method to get expected values + expected_investigation = investigation.to_dict() + + # Create actual object with all fields from database record + actual_investigation = { + 'providerId': investigation_data.providerId, + 'jurisdiction': investigation_data.jurisdiction, + 'investigationAgainst': investigation_data.investigationAgainst, + 'submittingUser': investigation_data.submittingUser, + 'investigationId': investigation_data.investigationId, + 'compact': investigation_data.compact, + 'creationDate': investigation_data.creationDate, + 'licenseType': investigation_data.licenseType, + 'type': investigation_data.type, + } + + # Pop dynamic fields from expected object + expected_investigation.pop('dateOfUpdate') + + self.assertEqual(expected_investigation, actual_investigation) + + def test_investigation_data_class_outputs_expected_database_object(self): + # check final snapshot of expected data + investigation = self.test_data_generator.generate_default_investigation() + investigation_data = investigation.serialize_to_database_record() + pk = 'aslp#PROVIDER#89a6377e-c3a5-40e5-bca5-317ec854c570' + sk = 'aslp#PROVIDER#privilege/ne/slp#INVESTIGATION#98765432-9876-9876-9876-987654321098' + # Pop dynamic field + investigation_data.pop('dateOfUpdate') + + self.assertEqual( + { + 'investigationAgainst': 'privilege', + 'investigationId': '98765432-9876-9876-9876-987654321098', + 'compact': 'aslp', + 'creationDate': '2024-11-08T23:59:59+00:00', + 'jurisdiction': 'ne', + 'licenseType': 'speech-language pathologist', + 'pk': pk, + 'providerId': '89a6377e-c3a5-40e5-bca5-317ec854c570', + 'sk': sk, + 'submittingUser': '12a6377e-c3a5-40e5-bca5-317ec854c556', + 'type': 'investigation', + }, + investigation_data, + ) + + # Properties should be consistent with db record + self.assertEqual(pk, investigation.pk) + self.assertEqual(sk, investigation.sk) + + +class TestInvestigationPatchRequestSchema(TstLambdas): + def test_validate_patch(self): + """Test validation of a PATCH request (empty body is valid)""" + from cc_common.data_model.schema.investigation.api import InvestigationPatchRequestSchema + + # PATCH schema has no required fields + result = InvestigationPatchRequestSchema().load({}) + self.assertIsInstance(result, dict) + + def test_validate_patch_with_encumbrance(self): + """Test validation of a PATCH request with encumbrance""" + from cc_common.data_model.schema.investigation.api import InvestigationPatchRequestSchema + + investigation_data = { + 'encumbrance': { + 'encumbranceEffectiveDate': '2024-03-15', + 'encumbranceType': 'suspension', + 'clinicalPrivilegeActionCategory': 'Unsafe Practice or Substandard Care', + } + } + result = InvestigationPatchRequestSchema().load(investigation_data) + self.assertIsInstance(result, dict) + + def test_validate_patch_with_unknown_fields(self): + """Test validation passes even with unknown fields (ForgivingSchema)""" + from cc_common.data_model.schema.investigation.api import InvestigationPatchRequestSchema + + # ForgivingSchema allows unknown fields + investigation_data = {'unsupportedField': 'bad'} + + # This should not raise an error + result = InvestigationPatchRequestSchema().load(investigation_data) + self.assertIsInstance(result, dict) diff --git a/backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_license.py b/backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_license.py index 2fd57fcdd..d251530bc 100644 --- a/backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_license.py +++ b/backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_license.py @@ -244,6 +244,22 @@ def test_hash_is_unique(self): # The hashes should now be different self.assertNotEqual(change_hash, schema.hash_changes(schema.dump(alternate_record))) + def test_invalid_if_missing_investigation_details_when_update_type_is_investigation(self): + from cc_common.data_model.schema.license.record import LicenseUpdateRecordSchema + + with open('tests/resources/dynamo/license-update.json') as f: + privilege_data = json.load(f) + # Privilege investigation updates require an 'investigationDetails' fields + privilege_data['updateType'] = 'investigation' + + with self.assertRaises(ValidationError) as context: + LicenseUpdateRecordSchema().load(privilege_data) + + self.assertEqual( + {'investigationDetails': ['This field is required when update was investigation type']}, + context.exception.messages, + ) + class TestLicenseIngestSchema(TstLambdas): def test_calculated_status_to_jurisdiction_uploaded_status(self): diff --git a/backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_privilege.py b/backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_privilege.py index c64355b47..e58a86270 100644 --- a/backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_privilege.py +++ b/backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_privilege.py @@ -191,6 +191,22 @@ def test_invalid_if_missing_deactivation_details_when_update_type_is_deactivatio context.exception.messages, ) + def test_invalid_if_missing_investigation_details_when_update_type_is_investigation(self): + from cc_common.data_model.schema.privilege.record import PrivilegeUpdateRecordSchema + + with open('tests/resources/dynamo/privilege-update.json') as f: + privilege_data = json.load(f) + # Privilege investigation updates require an 'investigationDetails' fields + privilege_data['updateType'] = 'investigation' + + with self.assertRaises(ValidationError) as context: + PrivilegeUpdateRecordSchema().load(privilege_data) + + self.assertEqual( + {'investigationDetails': ['This field is required when update was investigation type']}, + context.exception.messages, + ) + def test_valid_if_deactivation_details_present_when_update_type_is_deactivation(self): from cc_common.data_model.schema.common import UpdateCategory from cc_common.data_model.schema.privilege.record import PrivilegeUpdateRecordSchema diff --git a/backend/compact-connect/lambdas/python/common/tests/unit/test_investigation_event_bus_client.py b/backend/compact-connect/lambdas/python/common/tests/unit/test_investigation_event_bus_client.py new file mode 100644 index 000000000..c7e09d27a --- /dev/null +++ b/backend/compact-connect/lambdas/python/common/tests/unit/test_investigation_event_bus_client.py @@ -0,0 +1,365 @@ +import json +from datetime import datetime +from unittest.mock import MagicMock +from uuid import uuid4 + +from tests import TstLambdas + + +class TestInvestigationEventBusClient(TstLambdas): + def setUp(self): + from cc_common.config import config + from cc_common.event_bus_client import EventBusClient + + self.mock_events_client = MagicMock(name='events-client') + config.events_client = self.mock_events_client + + self.client = EventBusClient() + + def test_publish_privilege_investigation_event(self): + """Test publishing privilege investigation event""" + from cc_common.data_model.schema.common import InvestigationAgainstEnum + + provider_id = uuid4() + investigation_id = uuid4() + create_date = datetime.fromisoformat('2024-02-15T12:00:00+00:00') + + # Call the method + self.client.publish_investigation_event( + source='test.source', + compact='aslp', + provider_id=provider_id, + jurisdiction='ne', + license_type_abbreviation='slp', + create_date=create_date, + investigation_against=InvestigationAgainstEnum.PRIVILEGE, + investigation_id=investigation_id, + ) + + # Verify put_events was called + self.mock_events_client.put_events.assert_called_once() + + # Verify the event structure + call_args = self.mock_events_client.put_events.call_args[1] + entries = call_args['Entries'] + self.assertEqual(1, len(entries)) + + event = entries[0] + + # Create expected event structure (without Detail field) + expected_event = { + 'Source': 'test.source', + 'DetailType': 'privilege.investigation', + 'EventBusName': 'license-data-events', + } + + # Create expected detail structure + expected_detail = { + 'compact': 'aslp', + 'providerId': str(provider_id), + 'investigationId': str(investigation_id), + 'jurisdiction': 'ne', + 'licenseTypeAbbreviation': 'slp', + 'investigationAgainst': 'privilege', + } + + # Pop dynamic field from actual event + actual_event = event.copy() + actual_detail = json.loads(actual_event['Detail']) + actual_detail.pop('eventTime') + actual_event.pop('Detail') + + # Compare event structure and detail separately + self.assertEqual(expected_event, actual_event) + self.assertEqual(expected_detail, actual_detail) + + def test_publish_license_investigation_event(self): + """Test publishing license investigation event""" + from cc_common.data_model.schema.common import InvestigationAgainstEnum + + provider_id = uuid4() + investigation_id = uuid4() + create_date = datetime.fromisoformat('2024-02-15T12:00:00+00:00') + + # Call the method + self.client.publish_investigation_event( + source='test.source', + compact='aslp', + provider_id=provider_id, + jurisdiction='ne', + license_type_abbreviation='slp', + create_date=create_date, + investigation_against=InvestigationAgainstEnum.LICENSE, + investigation_id=investigation_id, + ) + + # Verify put_events was called + self.mock_events_client.put_events.assert_called_once() + + # Verify the event structure + call_args = self.mock_events_client.put_events.call_args[1] + entries = call_args['Entries'] + self.assertEqual(1, len(entries)) + + event = entries[0] + + # Create expected event structure (without Detail field) + expected_event = { + 'Source': 'test.source', + 'DetailType': 'license.investigation', + 'EventBusName': 'license-data-events', + } + + # Create expected detail structure + expected_detail = { + 'compact': 'aslp', + 'providerId': str(provider_id), + 'investigationId': str(investigation_id), + 'jurisdiction': 'ne', + 'licenseTypeAbbreviation': 'slp', + 'investigationAgainst': 'license', + } + + # Pop dynamic field from actual event + actual_event = event.copy() + actual_detail = json.loads(actual_event['Detail']) + actual_detail.pop('eventTime') + actual_event.pop('Detail') + + # Compare event structure and detail separately + self.assertEqual(expected_event, actual_event) + self.assertEqual(expected_detail, actual_detail) + + def test_publish_privilege_investigation_closed_event(self): + """Test publishing privilege investigation closed event""" + from cc_common.data_model.schema.common import InvestigationAgainstEnum + + provider_id = uuid4() + investigation_id = uuid4() + close_date = datetime.fromisoformat('2024-03-15T12:00:00+00:00') + + # Call the method + self.client.publish_investigation_closed_event( + source='test.source', + compact='aslp', + provider_id=provider_id, + jurisdiction='ne', + license_type_abbreviation='slp', + close_date=close_date, + investigation_against=InvestigationAgainstEnum.PRIVILEGE, + investigation_id=investigation_id, + ) + + # Verify put_events was called + self.mock_events_client.put_events.assert_called_once() + + # Verify the event structure + call_args = self.mock_events_client.put_events.call_args[1] + entries = call_args['Entries'] + self.assertEqual(1, len(entries)) + + event = entries[0] + + # Create expected event structure (without Detail field) + expected_event = { + 'Source': 'test.source', + 'DetailType': 'privilege.investigationClosed', + 'EventBusName': 'license-data-events', + } + + # Create expected detail structure + expected_detail = { + 'compact': 'aslp', + 'providerId': str(provider_id), + 'investigationId': str(investigation_id), + 'jurisdiction': 'ne', + 'licenseTypeAbbreviation': 'slp', + 'investigationAgainst': 'privilege', + } + + # Pop dynamic field from actual event + actual_event = event.copy() + actual_detail = json.loads(actual_event['Detail']) + actual_detail.pop('eventTime') + actual_event.pop('Detail') + + # Compare event structure and detail separately + self.assertEqual(expected_event, actual_event) + self.assertEqual(expected_detail, actual_detail) + + def test_publish_license_investigation_closed_event(self): + """Test publishing license investigation closed event""" + from cc_common.data_model.schema.common import InvestigationAgainstEnum + + provider_id = uuid4() + investigation_id = uuid4() + close_date = datetime.fromisoformat('2024-03-15T12:00:00+00:00') + + # Call the method + self.client.publish_investigation_closed_event( + source='test.source', + compact='aslp', + provider_id=provider_id, + jurisdiction='ne', + license_type_abbreviation='slp', + close_date=close_date, + investigation_against=InvestigationAgainstEnum.LICENSE, + investigation_id=investigation_id, + ) + + # Verify put_events was called + self.mock_events_client.put_events.assert_called_once() + + # Verify the event structure + call_args = self.mock_events_client.put_events.call_args[1] + entries = call_args['Entries'] + self.assertEqual(1, len(entries)) + + event = entries[0] + + # Create expected event structure (without Detail field) + expected_event = { + 'Source': 'test.source', + 'DetailType': 'license.investigationClosed', + 'EventBusName': 'license-data-events', + } + + # Create expected detail structure + expected_detail = { + 'compact': 'aslp', + 'providerId': str(provider_id), + 'investigationId': str(investigation_id), + 'jurisdiction': 'ne', + 'licenseTypeAbbreviation': 'slp', + 'investigationAgainst': 'license', + } + + # Pop dynamic field from actual event + actual_event = event.copy() + actual_detail = json.loads(actual_event['Detail']) + actual_detail.pop('eventTime') + actual_event.pop('Detail') + + # Compare event structure and detail separately + self.assertEqual(expected_event, actual_event) + self.assertEqual(expected_detail, actual_detail) + + def test_publish_privilege_investigation_event_with_batch_writer(self): + """Test publishing privilege investigation event with batch writer""" + from cc_common.data_model.schema.common import InvestigationAgainstEnum + + provider_id = uuid4() + investigation_id = uuid4() + create_date = datetime.fromisoformat('2024-02-15T12:00:00+00:00') + + # Mock batch writer + mock_batch_writer = MagicMock() + + # Call the method + self.client.publish_investigation_event( + source='test.source', + compact='aslp', + provider_id=provider_id, + jurisdiction='ne', + license_type_abbreviation='slp', + create_date=create_date, + investigation_against=InvestigationAgainstEnum.PRIVILEGE, + investigation_id=investigation_id, + event_batch_writer=mock_batch_writer, + ) + + # Verify put_events was NOT called directly + self.mock_events_client.put_events.assert_not_called() + + # Verify batch writer was used + mock_batch_writer.put_event.assert_called_once() + + def test_publish_license_investigation_event_with_batch_writer(self): + """Test publishing license investigation event with batch writer""" + from cc_common.data_model.schema.common import InvestigationAgainstEnum + + provider_id = uuid4() + investigation_id = uuid4() + create_date = datetime.fromisoformat('2024-02-15T12:00:00+00:00') + + # Mock batch writer + mock_batch_writer = MagicMock() + + # Call the method + self.client.publish_investigation_event( + source='test.source', + compact='aslp', + provider_id=provider_id, + jurisdiction='ne', + license_type_abbreviation='slp', + create_date=create_date, + investigation_against=InvestigationAgainstEnum.LICENSE, + investigation_id=investigation_id, + event_batch_writer=mock_batch_writer, + ) + + # Verify put_events was NOT called directly + self.mock_events_client.put_events.assert_not_called() + + # Verify batch writer was used + mock_batch_writer.put_event.assert_called_once() + + def test_publish_privilege_investigation_closed_event_with_batch_writer(self): + """Test publishing privilege investigation closed event with batch writer""" + from cc_common.data_model.schema.common import InvestigationAgainstEnum + + provider_id = uuid4() + investigation_id = uuid4() + close_date = datetime.fromisoformat('2024-03-15T12:00:00+00:00') + + # Mock batch writer + mock_batch_writer = MagicMock() + + # Call the method + self.client.publish_investigation_closed_event( + source='test.source', + compact='aslp', + provider_id=provider_id, + jurisdiction='ne', + license_type_abbreviation='slp', + close_date=close_date, + investigation_against=InvestigationAgainstEnum.PRIVILEGE, + investigation_id=investigation_id, + event_batch_writer=mock_batch_writer, + ) + + # Verify put_events was NOT called directly + self.mock_events_client.put_events.assert_not_called() + + # Verify batch writer was used + mock_batch_writer.put_event.assert_called_once() + + def test_publish_license_investigation_closed_event_with_batch_writer(self): + """Test publishing license investigation closed event with batch writer""" + from cc_common.data_model.schema.common import InvestigationAgainstEnum + + provider_id = uuid4() + investigation_id = uuid4() + close_date = datetime.fromisoformat('2024-03-15T12:00:00+00:00') + + # Mock batch writer + mock_batch_writer = MagicMock() + + # Call the method + self.client.publish_investigation_closed_event( + source='test.source', + compact='aslp', + provider_id=provider_id, + jurisdiction='ne', + license_type_abbreviation='slp', + close_date=close_date, + investigation_against=InvestigationAgainstEnum.LICENSE, + investigation_id=investigation_id, + event_batch_writer=mock_batch_writer, + ) + + # Verify put_events was NOT called directly + self.mock_events_client.put_events.assert_not_called() + + # Verify batch writer was used + mock_batch_writer.put_event.assert_called_once() diff --git a/backend/compact-connect/lambdas/python/compact-configuration/requirements-dev.txt b/backend/compact-connect/lambdas/python/compact-configuration/requirements-dev.txt index e30bfbb2a..c741541bc 100644 --- a/backend/compact-connect/lambdas/python/compact-configuration/requirements-dev.txt +++ b/backend/compact-connect/lambdas/python/compact-configuration/requirements-dev.txt @@ -1,27 +1,27 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/compact-configuration/requirements-dev.in +# pip-compile --no-emit-index-url --no-strip-extras lambdas/python/compact-configuration/requirements-dev.in # -boto3==1.40.35 +boto3==1.40.56 # via moto -botocore==1.40.35 +botocore==1.40.56 # via # boto3 # moto # s3transfer -certifi==2025.8.3 +certifi==2025.10.5 # via requests cffi==2.0.0 # via cryptography -charset-normalizer==3.4.3 +charset-normalizer==3.4.4 # via requests -cryptography==46.0.1 +cryptography==46.0.3 # via moto docker==7.1.0 # via moto -idna==3.10 +idna==3.11 # via requests jinja2==3.1.6 # via moto @@ -29,11 +29,11 @@ jmespath==1.0.1 # via # boto3 # botocore -markupsafe==3.0.2 +markupsafe==3.0.3 # via # jinja2 # werkzeug -moto[dynamodb,s3]==5.1.12 +moto[dynamodb,s3]==5.1.15 # via -r lambdas/python/compact-configuration/requirements-dev.in py-partiql-parser==0.6.1 # via moto @@ -43,7 +43,7 @@ python-dateutil==2.9.0.post0 # via # botocore # moto -pyyaml==6.0.2 +pyyaml==6.0.3 # via # moto # responses diff --git a/backend/compact-connect/lambdas/python/compact-configuration/requirements.txt b/backend/compact-connect/lambdas/python/compact-configuration/requirements.txt index f56b86777..4e0089cfc 100644 --- a/backend/compact-connect/lambdas/python/compact-configuration/requirements.txt +++ b/backend/compact-connect/lambdas/python/compact-configuration/requirements.txt @@ -1,6 +1,6 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/compact-configuration/requirements.in +# pip-compile --no-emit-index-url --no-strip-extras lambdas/python/compact-configuration/requirements.in # diff --git a/backend/compact-connect/lambdas/python/custom-resources/requirements-dev.txt b/backend/compact-connect/lambdas/python/custom-resources/requirements-dev.txt index 82c17d527..0ac54cfb6 100644 --- a/backend/compact-connect/lambdas/python/custom-resources/requirements-dev.txt +++ b/backend/compact-connect/lambdas/python/custom-resources/requirements-dev.txt @@ -1,27 +1,27 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/custom-resources/requirements-dev.in +# pip-compile --no-emit-index-url --no-strip-extras lambdas/python/custom-resources/requirements-dev.in # -boto3==1.40.35 +boto3==1.40.56 # via moto -botocore==1.40.35 +botocore==1.40.56 # via # boto3 # moto # s3transfer -certifi==2025.8.3 +certifi==2025.10.5 # via requests cffi==2.0.0 # via cryptography -charset-normalizer==3.4.3 +charset-normalizer==3.4.4 # via requests -cryptography==46.0.1 +cryptography==46.0.3 # via moto docker==7.1.0 # via moto -idna==3.10 +idna==3.11 # via requests jinja2==3.1.6 # via moto @@ -29,11 +29,11 @@ jmespath==1.0.1 # via # boto3 # botocore -markupsafe==3.0.2 +markupsafe==3.0.3 # via # jinja2 # werkzeug -moto[dynamodb,s3]==5.1.12 +moto[dynamodb,s3]==5.1.15 # via -r lambdas/python/custom-resources/requirements-dev.in py-partiql-parser==0.6.1 # via moto @@ -43,7 +43,7 @@ python-dateutil==2.9.0.post0 # via # botocore # moto -pyyaml==6.0.2 +pyyaml==6.0.3 # via # moto # responses diff --git a/backend/compact-connect/lambdas/python/custom-resources/requirements.txt b/backend/compact-connect/lambdas/python/custom-resources/requirements.txt index 6c98adb26..a4578b969 100644 --- a/backend/compact-connect/lambdas/python/custom-resources/requirements.txt +++ b/backend/compact-connect/lambdas/python/custom-resources/requirements.txt @@ -1,6 +1,6 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/custom-resources/requirements.in +# pip-compile --no-emit-index-url --no-strip-extras lambdas/python/custom-resources/requirements.in # diff --git a/backend/compact-connect/lambdas/python/data-events/handlers/investigation_events.py b/backend/compact-connect/lambdas/python/data-events/handlers/investigation_events.py new file mode 100644 index 000000000..e8e55c940 --- /dev/null +++ b/backend/compact-connect/lambdas/python/data-events/handlers/investigation_events.py @@ -0,0 +1,397 @@ +from typing import Any, Protocol +from uuid import UUID + +from cc_common.config import config, logger +from cc_common.data_model.provider_record_util import ProviderUserRecords +from cc_common.data_model.schema.data_event.api import InvestigationEventDetailSchema +from cc_common.data_model.schema.provider import ProviderData +from cc_common.email_service_client import InvestigationNotificationTemplateVariables +from cc_common.license_util import LicenseUtility +from cc_common.utils import sqs_handler + + +class JurisdictionNotificationMethod(Protocol): + """Protocol for Jurisdiction investigation notification methods.""" + + def __call__( + self, *, compact: str, jurisdiction: str, template_variables: InvestigationNotificationTemplateVariables + ) -> dict[str, Any]: ... + + +def _get_provider_records(compact: str, provider_id: str) -> tuple[ProviderUserRecords, ProviderData]: + """ + Retrieve and validate provider records for notification processing. + + :param compact: The compact identifier + :param provider_id: The provider ID + :return: Tuple of (provider_records, provider_record) + :raises Exception: If provider records cannot be retrieved + """ + try: + provider_records = config.data_client.get_provider_user_records( + compact=compact, + provider_id=provider_id, + ) + provider_record = provider_records.get_provider_record() + return provider_records, provider_record + except Exception as e: + logger.error('Failed to retrieve provider records for notification', exception=str(e)) + raise + + +def _send_primary_state_notification( + notification_method: JurisdictionNotificationMethod, + notification_type: str, + *, + provider_record: ProviderData, + jurisdiction: str, + compact: str, + **notification_kwargs, +) -> None: + """ + Send notification to the primary affected state. + + :param notification_method: The email service method to call + :param notification_type: Type of notification for logging + :param provider_record: The provider record + :param jurisdiction: The jurisdiction to notify + :param compact: The compact identifier + :param notification_kwargs: Additional arguments for the notification method + """ + logger.info(f'Sending {notification_type} notification to affected state', affected_jurisdiction=jurisdiction) + try: + notification_method( + compact=compact, + jurisdiction=jurisdiction, + template_variables=InvestigationNotificationTemplateVariables( + provider_first_name=provider_record.givenName, + provider_last_name=provider_record.familyName, + **notification_kwargs, + ), + ) + except Exception as e: + logger.error('Failed to send state notification', jurisdiction=jurisdiction, exception=str(e)) + raise + + +def _send_additional_state_notifications( + notification_method: JurisdictionNotificationMethod, + notification_type: str, + *, + provider_records: ProviderUserRecords, + provider_record: ProviderData, + provider_id: UUID, + excluded_jurisdiction: str, + compact: str, + **notification_kwargs, +) -> None: + """ + Send notifications to all other states where the provider has licenses or privileges. + + :param provider_records: The provider records collection + :param notification_method: The email service method to call + :param notification_type: Type of notification for logging + :param provider_record: The provider record + :param provider_id: The provider ID + :param excluded_jurisdiction: Jurisdiction to exclude from notifications + :param compact: The compact identifier + :param notification_kwargs: Additional arguments for the notification method + """ + # Query provider's records to find all states where they hold or have held licenses or privileges + all_licenses = provider_records.get_license_records() + all_privileges = provider_records.get_privilege_records() + + # Get unique jurisdictions (excluding the one already notified) + notification_jurisdictions = set() + for license_record in all_licenses: + if license_record.jurisdiction != excluded_jurisdiction: + notification_jurisdictions.add(license_record.jurisdiction) + for privilege_record in all_privileges: + if privilege_record.jurisdiction != excluded_jurisdiction: + notification_jurisdictions.add(privilege_record.jurisdiction) + + # Send notifications to all other states with provider licenses or privileges + template_variables = InvestigationNotificationTemplateVariables( + provider_first_name=provider_record.givenName, + provider_last_name=provider_record.familyName, + provider_id=provider_id, + **notification_kwargs, + ) + for notification_jurisdiction in notification_jurisdictions: + logger.info( + f'Sending {notification_type} notification to other state', + notification_jurisdiction=notification_jurisdiction, + ) + try: + notification_method( + compact=compact, + jurisdiction=notification_jurisdiction, + template_variables=template_variables, + ) + except Exception as e: + logger.error( + 'Failed to send notification to other state', + notification_jurisdiction=notification_jurisdiction, + exception=str(e), + ) + raise + + +@sqs_handler +def license_investigation_notification_listener(message: dict): + """ + Handle license investigation events by sending notifications. + + This handler processes 'license.investigation' events and sends notifications + to the affected provider and relevant states. + """ + detail_schema = InvestigationEventDetailSchema() + detail = detail_schema.load(message['detail']) + + compact = detail['compact'] + provider_id = detail['providerId'] + jurisdiction = detail['jurisdiction'] + license_type_abbreviation = detail['licenseTypeAbbreviation'] + event_time = detail['eventTime'] + + with logger.append_context_keys( + compact=compact, + provider_id=provider_id, + jurisdiction=jurisdiction, + license_type_abbreviation=license_type_abbreviation, + event_time=event_time, + ): + logger.info('Processing license investigation event') + + # Get license type name from abbreviation (lookup once at the top) + license_type_name = LicenseUtility.get_license_type_by_abbreviation(compact, license_type_abbreviation).name + + # Get provider records to gather notification targets and provider information + provider_records, provider_record = _get_provider_records(compact, provider_id) + + # State Notifications + # Note: We do NOT send notifications to providers for investigations + # Send notification to the state where the license is under investigation + _send_primary_state_notification( + config.email_service_client.send_license_investigation_state_notification_email, + 'license investigation', + provider_record=provider_record, + provider_id=provider_id, + jurisdiction=jurisdiction, + compact=compact, + investigation_jurisdiction=jurisdiction, + license_type=license_type_name, + ) + + # Send notifications to all other states with provider licenses or privileges + _send_additional_state_notifications( + config.email_service_client.send_license_investigation_state_notification_email, + 'license investigation', + provider_records=provider_records, + provider_record=provider_record, + provider_id=provider_id, + excluded_jurisdiction=jurisdiction, + compact=compact, + investigation_jurisdiction=jurisdiction, + license_type=license_type_name, + ) + + logger.info('Successfully processed license investigation event') + + +@sqs_handler +def license_investigation_closed_notification_listener(message: dict): + """ + Handle license investigation closed events by sending notifications. + + This handler processes 'license.investigationClosed' events and sends notifications + to the affected provider and relevant states. + """ + detail_schema = InvestigationEventDetailSchema() + detail = detail_schema.load(message['detail']) + + compact = detail['compact'] + provider_id = detail['providerId'] + jurisdiction = detail['jurisdiction'] + license_type_abbreviation = detail['licenseTypeAbbreviation'] + event_time = detail['eventTime'] + + with logger.append_context_keys( + compact=compact, + provider_id=provider_id, + jurisdiction=jurisdiction, + license_type_abbreviation=license_type_abbreviation, + event_time=event_time, + ): + logger.info('Processing license investigation closed event') + + # If an encumbrance resulted from the investigation, we will let the encumbrance notification suffice. + # This is determined by the presence of an 'adverseActionId' in the event detail. + if detail.get('adverseActionId'): + logger.info('Investigation closed with an encumbrance, skipping investigation closed notifications.') + return + + # Get license type name from abbreviation (lookup once at the top) + license_type_name = LicenseUtility.get_license_type_by_abbreviation(compact, license_type_abbreviation).name + + # Get provider records to gather notification targets and provider information + provider_records, provider_record = _get_provider_records(compact, provider_id) + + # State Notifications + # Note: We do NOT send notifications to providers for investigations + # Send notification to the state where the license investigation was closed + _send_primary_state_notification( + config.email_service_client.send_license_investigation_closed_state_notification_email, + 'license investigation closed', + provider_record=provider_record, + provider_id=provider_id, + jurisdiction=jurisdiction, + compact=compact, + investigation_jurisdiction=jurisdiction, + license_type=license_type_name, + ) + + # Send notifications to all other states with provider licenses or privileges + _send_additional_state_notifications( + config.email_service_client.send_license_investigation_closed_state_notification_email, + 'license investigation closed', + provider_records=provider_records, + provider_record=provider_record, + provider_id=provider_id, + excluded_jurisdiction=jurisdiction, + compact=compact, + investigation_jurisdiction=jurisdiction, + license_type=license_type_name, + ) + + logger.info('Successfully processed license investigation closed event') + + +@sqs_handler +def privilege_investigation_notification_listener(message: dict): + """ + Handle privilege investigation events by sending notifications. + + This handler processes 'privilege.investigation' events and sends notifications + to the affected provider and relevant states. + """ + detail_schema = InvestigationEventDetailSchema() + detail = detail_schema.load(message['detail']) + + compact = detail['compact'] + provider_id = detail['providerId'] + jurisdiction = detail['jurisdiction'] + license_type_abbreviation = detail['licenseTypeAbbreviation'] + event_time = detail['eventTime'] + + with logger.append_context_keys( + compact=compact, + provider_id=provider_id, + jurisdiction=jurisdiction, + license_type_abbreviation=license_type_abbreviation, + event_time=event_time, + ): + logger.info('Processing privilege investigation event') + + # Get license type name from abbreviation (lookup once at the top) + license_type_name = LicenseUtility.get_license_type_by_abbreviation(compact, license_type_abbreviation).name + + # Get provider records to gather notification targets and provider information + provider_records, provider_record = _get_provider_records(compact, provider_id) + + # State Notifications + # Note: We do NOT send notifications to providers for investigations + # Send notification to the state where the privilege is under investigation + _send_primary_state_notification( + config.email_service_client.send_privilege_investigation_state_notification_email, + 'privilege investigation', + provider_record=provider_record, + provider_id=provider_id, + jurisdiction=jurisdiction, + compact=compact, + investigation_jurisdiction=jurisdiction, + license_type=license_type_name, + ) + + # Send notifications to all other states with provider licenses or privileges + _send_additional_state_notifications( + config.email_service_client.send_privilege_investigation_state_notification_email, + 'privilege investigation', + provider_records=provider_records, + provider_record=provider_record, + provider_id=provider_id, + excluded_jurisdiction=jurisdiction, + compact=compact, + investigation_jurisdiction=jurisdiction, + license_type=license_type_name, + ) + + logger.info('Successfully processed privilege investigation event') + + +@sqs_handler +def privilege_investigation_closed_notification_listener(message: dict): + """ + Handle privilege investigation closed events by sending notifications. + + This handler processes 'privilege.investigationClosed' events and sends notifications + to the affected provider and relevant states. + """ + detail_schema = InvestigationEventDetailSchema() + detail = detail_schema.load(message['detail']) + + compact = detail['compact'] + provider_id = detail['providerId'] + jurisdiction = detail['jurisdiction'] + license_type_abbreviation = detail['licenseTypeAbbreviation'] + event_time = detail['eventTime'] + + with logger.append_context_keys( + compact=compact, + provider_id=provider_id, + jurisdiction=jurisdiction, + license_type_abbreviation=license_type_abbreviation, + event_time=event_time, + ): + logger.info('Processing privilege investigation closed event') + + # If an encumbrance resulted from the investigation, we will let the encumbrance notification suffice. + # This is determined by the presence of an 'adverseActionId' in the event detail. + if detail.get('adverseActionId'): + logger.info('Investigation closed with an encumbrance, skipping investigation closed notifications.') + return + + # Get license type name from abbreviation (lookup once at the top) + license_type_name = LicenseUtility.get_license_type_by_abbreviation(compact, license_type_abbreviation).name + + # Get provider records to gather notification targets and provider information + provider_records, provider_record = _get_provider_records(compact, provider_id) + + # State Notifications + # Note: We do NOT send notifications to providers for investigations + # Send notification to the state where the privilege investigation was closed + _send_primary_state_notification( + config.email_service_client.send_privilege_investigation_closed_state_notification_email, + 'privilege investigation closed', + provider_record=provider_record, + provider_id=provider_id, + jurisdiction=jurisdiction, + compact=compact, + investigation_jurisdiction=jurisdiction, + license_type=license_type_name, + ) + + # Send notifications to all other states with provider licenses or privileges + _send_additional_state_notifications( + config.email_service_client.send_privilege_investigation_closed_state_notification_email, + 'privilege investigation closed', + provider_records=provider_records, + provider_record=provider_record, + provider_id=provider_id, + excluded_jurisdiction=jurisdiction, + compact=compact, + investigation_jurisdiction=jurisdiction, + license_type=license_type_name, + ) + + logger.info('Successfully processed privilege investigation closed event') diff --git a/backend/compact-connect/lambdas/python/data-events/requirements-dev.txt b/backend/compact-connect/lambdas/python/data-events/requirements-dev.txt index c92146177..e081f34c9 100644 --- a/backend/compact-connect/lambdas/python/data-events/requirements-dev.txt +++ b/backend/compact-connect/lambdas/python/data-events/requirements-dev.txt @@ -1,27 +1,27 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/data-events/requirements-dev.in +# pip-compile --no-emit-index-url --no-strip-extras lambdas/python/data-events/requirements-dev.in # -boto3==1.40.35 +boto3==1.40.56 # via moto -botocore==1.40.35 +botocore==1.40.56 # via # boto3 # moto # s3transfer -certifi==2025.8.3 +certifi==2025.10.5 # via requests cffi==2.0.0 # via cryptography -charset-normalizer==3.4.3 +charset-normalizer==3.4.4 # via requests -cryptography==46.0.1 +cryptography==46.0.3 # via moto docker==7.1.0 # via moto -idna==3.10 +idna==3.11 # via requests jinja2==3.1.6 # via moto @@ -29,11 +29,11 @@ jmespath==1.0.1 # via # boto3 # botocore -markupsafe==3.0.2 +markupsafe==3.0.3 # via # jinja2 # werkzeug -moto[dynamodb,s3]==5.1.12 +moto[dynamodb,s3]==5.1.15 # via -r lambdas/python/data-events/requirements-dev.in py-partiql-parser==0.6.1 # via moto @@ -43,7 +43,7 @@ python-dateutil==2.9.0.post0 # via # botocore # moto -pyyaml==6.0.2 +pyyaml==6.0.3 # via # moto # responses diff --git a/backend/compact-connect/lambdas/python/data-events/requirements.txt b/backend/compact-connect/lambdas/python/data-events/requirements.txt index da006c453..7df16379d 100644 --- a/backend/compact-connect/lambdas/python/data-events/requirements.txt +++ b/backend/compact-connect/lambdas/python/data-events/requirements.txt @@ -1,6 +1,6 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/data-events/requirements.in +# pip-compile --no-emit-index-url --no-strip-extras lambdas/python/data-events/requirements.in # diff --git a/backend/compact-connect/lambdas/python/data-events/tests/function/test_investigation_events.py b/backend/compact-connect/lambdas/python/data-events/tests/function/test_investigation_events.py new file mode 100644 index 000000000..526f111b2 --- /dev/null +++ b/backend/compact-connect/lambdas/python/data-events/tests/function/test_investigation_events.py @@ -0,0 +1,598 @@ +import json +from datetime import datetime +from unittest.mock import patch +from uuid import UUID, uuid4 + +from common_test.test_constants import ( + DEFAULT_COMPACT, + DEFAULT_DATE_OF_UPDATE_TIMESTAMP, + DEFAULT_LICENSE_JURISDICTION, + DEFAULT_LICENSE_TYPE_ABBREVIATION, + DEFAULT_PRIVILEGE_JURISDICTION, + 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 TestInvestigationEvents(TstFunction): + """Test suite for investigation event handlers.""" + + def _generate_license_investigation_message(self, message_overrides=None): + """Generate a test SQS message for license investigation events.""" + message = { + 'detail': { + 'compact': DEFAULT_COMPACT, + 'providerId': DEFAULT_PROVIDER_ID, + 'jurisdiction': DEFAULT_LICENSE_JURISDICTION, + 'licenseTypeAbbreviation': DEFAULT_LICENSE_TYPE_ABBREVIATION, + 'eventTime': DEFAULT_DATE_OF_UPDATE_TIMESTAMP, + 'investigationAgainst': 'license', + 'investigationId': str(uuid4()), + } + } + if message_overrides: + message['detail'].update(message_overrides) + return message + + def _generate_license_investigation_closed_message(self, message_overrides=None): + """Generate a test SQS message for license investigation closed events.""" + message = { + 'detail': { + 'compact': DEFAULT_COMPACT, + 'providerId': DEFAULT_PROVIDER_ID, + 'jurisdiction': DEFAULT_LICENSE_JURISDICTION, + 'licenseTypeAbbreviation': DEFAULT_LICENSE_TYPE_ABBREVIATION, + 'eventTime': DEFAULT_DATE_OF_UPDATE_TIMESTAMP, + 'investigationAgainst': 'license', + 'investigationId': str(uuid4()), + } + } + if message_overrides: + message['detail'].update(message_overrides) + return message + + def _generate_privilege_investigation_message(self, message_overrides=None): + """Generate a test SQS message for privilege investigation events.""" + message = { + 'detail': { + 'compact': DEFAULT_COMPACT, + 'providerId': DEFAULT_PROVIDER_ID, + 'jurisdiction': DEFAULT_PRIVILEGE_JURISDICTION, + 'licenseTypeAbbreviation': DEFAULT_LICENSE_TYPE_ABBREVIATION, + 'eventTime': DEFAULT_DATE_OF_UPDATE_TIMESTAMP, + 'investigationAgainst': 'privilege', + 'investigationId': str(uuid4()), + } + } + if message_overrides: + message['detail'].update(message_overrides) + return message + + def _generate_privilege_investigation_closed_message(self, message_overrides=None): + """Generate a test SQS message for privilege investigation closed events.""" + message = { + 'detail': { + 'compact': DEFAULT_COMPACT, + 'providerId': DEFAULT_PROVIDER_ID, + 'jurisdiction': DEFAULT_PRIVILEGE_JURISDICTION, + 'licenseTypeAbbreviation': DEFAULT_LICENSE_TYPE_ABBREVIATION, + 'eventTime': DEFAULT_DATE_OF_UPDATE_TIMESTAMP, + 'investigationAgainst': 'privilege', + 'investigationId': str(uuid4()), + } + } + if message_overrides: + message['detail'].update(message_overrides) + return message + + def _create_sqs_event(self, message): + """Create a proper SQS event structure with the message in the body.""" + return {'Records': [{'messageId': '123', 'body': json.dumps(message)}]} + + @patch('cc_common.email_service_client.EmailServiceClient.send_license_investigation_state_notification_email') + def test_license_investigation_listener_processes_event_with_registered_provider(self, mock_state_email): + """Test that license investigation listener processes events for registered providers.""" + from cc_common.email_service_client import InvestigationNotificationTemplateVariables + from handlers.investigation_events import license_investigation_notification_listener + + # Set up test data with registered provider + self.test_data_generator.put_default_provider_record_in_provider_table( + value_overrides={'compactConnectRegisteredEmailAddress': 'provider@example.com'} + ) + + # Add the license that is under investigation + self.test_data_generator.put_default_license_record_in_provider_table() + + # Create additional licenses and privileges for notification testing + self.test_data_generator.put_default_license_record_in_provider_table( + value_overrides={ + 'jurisdiction': 'co', + 'jurisdictionUploadedLicenseStatus': 'active', + } + ) + self.test_data_generator.put_default_privilege_record_in_provider_table( + value_overrides={ + 'jurisdiction': 'ky', + 'administratorSetStatus': 'active', + } + ) + + message = self._generate_license_investigation_message() + event = self._create_sqs_event(message) + + # Execute the handler + result = license_investigation_notification_listener(event, self.mock_context) + + # Should succeed with no batch failures + self.assertEqual({'batchItemFailures': []}, result) + + # Verify state notifications (investigation state + other states with active licenses/privileges) + # Note: We do NOT send provider notifications for investigations + expected_template_variables_oh = InvestigationNotificationTemplateVariables( + provider_first_name='Björk', + provider_last_name='Guðmundsdóttir', + investigation_jurisdiction=DEFAULT_LICENSE_JURISDICTION, + license_type='speech-language pathologist', + provider_id=UUID(DEFAULT_PROVIDER_ID), + ) + expected_template_variables_co = InvestigationNotificationTemplateVariables( + provider_first_name='Björk', + provider_last_name='Guðmundsdóttir', + investigation_jurisdiction=DEFAULT_LICENSE_JURISDICTION, + license_type='speech-language pathologist', + provider_id=UUID(DEFAULT_PROVIDER_ID), + ) + expected_template_variables_ky = InvestigationNotificationTemplateVariables( + provider_first_name='Björk', + provider_last_name='Guðmundsdóttir', + investigation_jurisdiction=DEFAULT_LICENSE_JURISDICTION, + license_type='speech-language pathologist', + provider_id=UUID(DEFAULT_PROVIDER_ID), + ) + expected_state_calls = [ + # State 'oh' (investigation jurisdiction) + { + 'compact': DEFAULT_COMPACT, + 'jurisdiction': DEFAULT_LICENSE_JURISDICTION, + 'template_variables': expected_template_variables_oh, + }, + # State 'co' (active license jurisdiction) + { + 'compact': DEFAULT_COMPACT, + 'jurisdiction': 'co', + 'template_variables': expected_template_variables_co, + }, + # State 'ky' (active privilege jurisdiction) + { + 'compact': DEFAULT_COMPACT, + 'jurisdiction': 'ky', + 'template_variables': expected_template_variables_ky, + }, + ] + + # Verify all state notifications were sent + self.assertEqual(3, mock_state_email.call_count) + actual_state_calls = [call.kwargs for call in mock_state_email.call_args_list] + + # Sort both lists for comparison + expected_state_calls_sorted = sorted(expected_state_calls, key=lambda x: x['jurisdiction']) + actual_state_calls_sorted = sorted(actual_state_calls, key=lambda x: x['jurisdiction']) + + self.assertEqual(expected_state_calls_sorted, actual_state_calls_sorted) + + @patch( + 'cc_common.email_service_client.EmailServiceClient.send_license_investigation_closed_state_notification_email' + ) + def test_license_investigation_closed_listener_processes_event_with_registered_provider(self, mock_state_email): + """Test that license investigation closed listener processes events for registered providers.""" + from cc_common.email_service_client import InvestigationNotificationTemplateVariables + from handlers.investigation_events import license_investigation_closed_notification_listener + + # Set up test data with registered provider + self.test_data_generator.put_default_provider_record_in_provider_table( + value_overrides={'compactConnectRegisteredEmailAddress': 'provider@example.com'} + ) + + # Add the license that was under investigation + self.test_data_generator.put_default_license_record_in_provider_table() + + # Create additional licenses and privileges for notification testing + self.test_data_generator.put_default_license_record_in_provider_table( + value_overrides={ + 'jurisdiction': 'co', + 'jurisdictionUploadedLicenseStatus': 'active', + } + ) + self.test_data_generator.put_default_privilege_record_in_provider_table( + value_overrides={ + 'jurisdiction': 'ky', + 'administratorSetStatus': 'active', + } + ) + + message = self._generate_license_investigation_closed_message() + event = self._create_sqs_event(message) + + # Execute the handler + result = license_investigation_closed_notification_listener(event, self.mock_context) + + # Should succeed with no batch failures + self.assertEqual({'batchItemFailures': []}, result) + + # Verify state notifications (investigation state + other states with active licenses/privileges) + # Note: We do NOT send provider notifications for investigations + expected_template_variables_oh = InvestigationNotificationTemplateVariables( + provider_first_name='Björk', + provider_last_name='Guðmundsdóttir', + investigation_jurisdiction=DEFAULT_LICENSE_JURISDICTION, + license_type='speech-language pathologist', + provider_id=UUID(DEFAULT_PROVIDER_ID), + ) + expected_template_variables_co = InvestigationNotificationTemplateVariables( + provider_first_name='Björk', + provider_last_name='Guðmundsdóttir', + investigation_jurisdiction=DEFAULT_LICENSE_JURISDICTION, + license_type='speech-language pathologist', + provider_id=UUID(DEFAULT_PROVIDER_ID), + ) + expected_template_variables_ky = InvestigationNotificationTemplateVariables( + provider_first_name='Björk', + provider_last_name='Guðmundsdóttir', + investigation_jurisdiction=DEFAULT_LICENSE_JURISDICTION, + license_type='speech-language pathologist', + provider_id=UUID(DEFAULT_PROVIDER_ID), + ) + expected_state_calls = [ + # State 'oh' (investigation jurisdiction) + { + 'compact': DEFAULT_COMPACT, + 'jurisdiction': DEFAULT_LICENSE_JURISDICTION, + 'template_variables': expected_template_variables_oh, + }, + # State 'co' (active license jurisdiction) + { + 'compact': DEFAULT_COMPACT, + 'jurisdiction': 'co', + 'template_variables': expected_template_variables_co, + }, + # State 'ky' (active privilege jurisdiction) + { + 'compact': DEFAULT_COMPACT, + 'jurisdiction': 'ky', + 'template_variables': expected_template_variables_ky, + }, + ] + + # Verify all state notifications were sent + self.assertEqual(3, mock_state_email.call_count) + actual_state_calls = [call.kwargs for call in mock_state_email.call_args_list] + + # Sort both lists for comparison + expected_state_calls_sorted = sorted(expected_state_calls, key=lambda x: x['jurisdiction']) + actual_state_calls_sorted = sorted(actual_state_calls, key=lambda x: x['jurisdiction']) + + self.assertEqual(expected_state_calls_sorted, actual_state_calls_sorted) + + @patch('cc_common.email_service_client.EmailServiceClient.send_privilege_investigation_state_notification_email') + def test_privilege_investigation_listener_processes_event_with_registered_provider(self, mock_state_email): + """Test that privilege investigation listener processes events for registered providers.""" + from cc_common.email_service_client import InvestigationNotificationTemplateVariables + from handlers.investigation_events import privilege_investigation_notification_listener + + # Set up test data with registered provider + self.test_data_generator.put_default_provider_record_in_provider_table( + value_overrides={'compactConnectRegisteredEmailAddress': 'provider@example.com'} + ) + + # Add the privilege that is under investigation + self.test_data_generator.put_default_privilege_record_in_provider_table() + + # Create additional licenses and privileges for notification testing + self.test_data_generator.put_default_license_record_in_provider_table( + value_overrides={ + 'jurisdiction': 'co', + 'jurisdictionUploadedLicenseStatus': 'active', + } + ) + self.test_data_generator.put_default_privilege_record_in_provider_table( + value_overrides={ + 'jurisdiction': 'ky', + 'administratorSetStatus': 'active', + } + ) + + message = self._generate_privilege_investigation_message() + event = self._create_sqs_event(message) + + # Execute the handler + result = privilege_investigation_notification_listener(event, self.mock_context) + + # Should succeed with no batch failures + self.assertEqual({'batchItemFailures': []}, result) + + # Verify state notifications (investigation state + other states with active licenses/privileges) + # Note: We do NOT send provider notifications for investigations + expected_template_variables_ne = InvestigationNotificationTemplateVariables( + provider_first_name='Björk', + provider_last_name='Guðmundsdóttir', + investigation_jurisdiction=DEFAULT_PRIVILEGE_JURISDICTION, + license_type='speech-language pathologist', + provider_id=UUID(DEFAULT_PROVIDER_ID), + ) + expected_template_variables_co = InvestigationNotificationTemplateVariables( + provider_first_name='Björk', + provider_last_name='Guðmundsdóttir', + investigation_jurisdiction=DEFAULT_PRIVILEGE_JURISDICTION, + license_type='speech-language pathologist', + provider_id=UUID(DEFAULT_PROVIDER_ID), + ) + expected_template_variables_ky = InvestigationNotificationTemplateVariables( + provider_first_name='Björk', + provider_last_name='Guðmundsdóttir', + investigation_jurisdiction=DEFAULT_PRIVILEGE_JURISDICTION, + license_type='speech-language pathologist', + provider_id=UUID(DEFAULT_PROVIDER_ID), + ) + expected_state_calls = [ + # State 'ne' (investigation jurisdiction) + { + 'compact': DEFAULT_COMPACT, + 'jurisdiction': DEFAULT_PRIVILEGE_JURISDICTION, + 'template_variables': expected_template_variables_ne, + }, + # State 'co' (active license jurisdiction) + { + 'compact': DEFAULT_COMPACT, + 'jurisdiction': 'co', + 'template_variables': expected_template_variables_co, + }, + # State 'ky' (active privilege jurisdiction) + { + 'compact': DEFAULT_COMPACT, + 'jurisdiction': 'ky', + 'template_variables': expected_template_variables_ky, + }, + ] + + # Verify all state notifications were sent + self.assertEqual(3, mock_state_email.call_count) + actual_state_calls = [call.kwargs for call in mock_state_email.call_args_list] + + # Sort both lists for comparison + expected_state_calls_sorted = sorted(expected_state_calls, key=lambda x: x['jurisdiction']) + actual_state_calls_sorted = sorted(actual_state_calls, key=lambda x: x['jurisdiction']) + + self.assertEqual(expected_state_calls_sorted, actual_state_calls_sorted) + + @patch( + 'cc_common.email_service_client.EmailServiceClient.send_privilege_investigation_closed_state_notification_email' + ) + def test_privilege_investigation_closed_listener_processes_event_with_registered_provider(self, mock_state_email): + """Test that privilege investigation closed listener processes events for registered providers.""" + from cc_common.email_service_client import InvestigationNotificationTemplateVariables + from handlers.investigation_events import privilege_investigation_closed_notification_listener + + # Set up test data with registered provider + self.test_data_generator.put_default_provider_record_in_provider_table( + value_overrides={'compactConnectRegisteredEmailAddress': 'provider@example.com'} + ) + + # Add the privilege that was under investigation + self.test_data_generator.put_default_privilege_record_in_provider_table() + + # Create additional licenses and privileges for notification testing + self.test_data_generator.put_default_license_record_in_provider_table( + value_overrides={ + 'jurisdiction': 'co', + 'jurisdictionUploadedLicenseStatus': 'active', + } + ) + self.test_data_generator.put_default_privilege_record_in_provider_table( + value_overrides={ + 'jurisdiction': 'ky', + 'administratorSetStatus': 'active', + } + ) + + message = self._generate_privilege_investigation_closed_message() + event = self._create_sqs_event(message) + + # Execute the handler + result = privilege_investigation_closed_notification_listener(event, self.mock_context) + + # Should succeed with no batch failures + self.assertEqual({'batchItemFailures': []}, result) + + # Verify state notifications (investigation state + other states with active licenses/privileges) + # Note: We do NOT send provider notifications for investigations + expected_template_variables_ne = InvestigationNotificationTemplateVariables( + provider_first_name='Björk', + provider_last_name='Guðmundsdóttir', + investigation_jurisdiction=DEFAULT_PRIVILEGE_JURISDICTION, + license_type='speech-language pathologist', + provider_id=UUID(DEFAULT_PROVIDER_ID), + ) + expected_template_variables_co = InvestigationNotificationTemplateVariables( + provider_first_name='Björk', + provider_last_name='Guðmundsdóttir', + investigation_jurisdiction=DEFAULT_PRIVILEGE_JURISDICTION, + license_type='speech-language pathologist', + provider_id=UUID(DEFAULT_PROVIDER_ID), + ) + expected_template_variables_ky = InvestigationNotificationTemplateVariables( + provider_first_name='Björk', + provider_last_name='Guðmundsdóttir', + investigation_jurisdiction=DEFAULT_PRIVILEGE_JURISDICTION, + license_type='speech-language pathologist', + provider_id=UUID(DEFAULT_PROVIDER_ID), + ) + expected_state_calls = [ + # State 'ne' (investigation jurisdiction) + { + 'compact': DEFAULT_COMPACT, + 'jurisdiction': DEFAULT_PRIVILEGE_JURISDICTION, + 'template_variables': expected_template_variables_ne, + }, + # State 'co' (active license jurisdiction) + { + 'compact': DEFAULT_COMPACT, + 'jurisdiction': 'co', + 'template_variables': expected_template_variables_co, + }, + # State 'ky' (active privilege jurisdiction) + { + 'compact': DEFAULT_COMPACT, + 'jurisdiction': 'ky', + 'template_variables': expected_template_variables_ky, + }, + ] + + # Verify all state notifications were sent + self.assertEqual(3, mock_state_email.call_count) + actual_state_calls = [call.kwargs for call in mock_state_email.call_args_list] + + # Sort both lists for comparison + expected_state_calls_sorted = sorted(expected_state_calls, key=lambda x: x['jurisdiction']) + actual_state_calls_sorted = sorted(actual_state_calls, key=lambda x: x['jurisdiction']) + + self.assertEqual(expected_state_calls_sorted, actual_state_calls_sorted) + + def test_license_investigation_listener_handles_missing_provider_records(self): + """Test that license investigation listener handles missing provider records gracefully.""" + from handlers.investigation_events import license_investigation_notification_listener + + # Don't set up any test data - provider records will be missing + message = self._generate_license_investigation_message() + event = self._create_sqs_event(message) + + # SQS handler wrapper catches exceptions and returns batch item failures + result = license_investigation_notification_listener(event, self.mock_context) + + # Should return batch item failure for the message + self.assertEqual(result['batchItemFailures'][0]['itemIdentifier'], '123') + + def test_privilege_investigation_listener_handles_missing_provider_records(self): + """Test that privilege investigation listener handles missing provider records gracefully.""" + from handlers.investigation_events import privilege_investigation_notification_listener + + # Don't set up any test data - provider records will be missing + message = self._generate_privilege_investigation_message() + event = self._create_sqs_event(message) + + # SQS handler wrapper catches exceptions and returns batch item failures + result = privilege_investigation_notification_listener(event, self.mock_context) + + # Should return batch item failure for the message + self.assertEqual(result['batchItemFailures'][0]['itemIdentifier'], '123') + + @patch('cc_common.email_service_client.EmailServiceClient.send_license_investigation_state_notification_email') + def test_license_investigation_listener_handles_email_service_failure(self, mock_state_email): + """Test that license investigation listener handles email service failures gracefully.""" + from handlers.investigation_events import license_investigation_notification_listener + + # Set up test data + self.test_data_generator.put_default_provider_record_in_provider_table( + value_overrides={'compactConnectRegisteredEmailAddress': 'provider@example.com'} + ) + self.test_data_generator.put_default_license_record_in_provider_table() + + # Make the email service raise an exception + mock_state_email.side_effect = Exception('Email service failure') + + message = self._generate_license_investigation_message() + event = self._create_sqs_event(message) + + # SQS handler wrapper catches exceptions and returns batch item failures + result = license_investigation_notification_listener(event, self.mock_context) + + # Should return batch item failure for the message + self.assertEqual(result['batchItemFailures'][0]['itemIdentifier'], '123') + + @patch('cc_common.email_service_client.EmailServiceClient.send_privilege_investigation_state_notification_email') + def test_privilege_investigation_listener_handles_email_service_failure(self, mock_state_email): + """Test that privilege investigation listener handles email service failures gracefully.""" + from handlers.investigation_events import privilege_investigation_notification_listener + + # Set up test data + self.test_data_generator.put_default_provider_record_in_provider_table( + value_overrides={'compactConnectRegisteredEmailAddress': 'provider@example.com'} + ) + self.test_data_generator.put_default_privilege_record_in_provider_table() + + # Make the email service raise an exception + mock_state_email.side_effect = Exception('Email service failure') + + message = self._generate_privilege_investigation_message() + event = self._create_sqs_event(message) + + # SQS handler wrapper catches exceptions and returns batch item failures + result = privilege_investigation_notification_listener(event, self.mock_context) + + # Should return batch item failure for the message + self.assertEqual(result['batchItemFailures'][0]['itemIdentifier'], '123') + + @patch( + 'cc_common.email_service_client.EmailServiceClient.send_license_investigation_closed_state_notification_email' + ) + def test_license_investigation_closed_listener_skips_notifications_when_encumbrance_exists(self, mock_state_email): + """ + Test that license investigation closed listener does NOT send notifications when adverseActionId is present. + When an investigation closes with an encumbrance, we rely on the encumbrance notification instead. + """ + from handlers.investigation_events import license_investigation_closed_notification_listener + + # Set up test data with registered provider + self.test_data_generator.put_default_provider_record_in_provider_table( + value_overrides={'compactConnectRegisteredEmailAddress': 'provider@example.com'} + ) + self.test_data_generator.put_default_license_record_in_provider_table() + + # Create message with adverseActionId (indicating an encumbrance was created) + message = self._generate_license_investigation_closed_message() + message['detail']['adverseActionId'] = str(uuid4()) + event = self._create_sqs_event(message) + + # Execute the handler + result = license_investigation_closed_notification_listener(event, self.mock_context) + + # Should succeed with no batch failures + self.assertEqual({'batchItemFailures': []}, result) + + # Verify NO notifications were sent (encumbrance notification will handle it) + mock_state_email.assert_not_called() + + @patch( + 'cc_common.email_service_client.EmailServiceClient.send_privilege_investigation_closed_state_notification_email' + ) + def test_privilege_investigation_closed_listener_skips_notifications_when_encumbrance_exists( + self, mock_state_email + ): + """ + Test that privilege investigation closed listener does NOT send notifications when adverseActionId is present. + When an investigation closes with an encumbrance, we rely on the encumbrance notification instead. + """ + from handlers.investigation_events import privilege_investigation_closed_notification_listener + + # Set up test data with registered provider + self.test_data_generator.put_default_provider_record_in_provider_table( + value_overrides={'compactConnectRegisteredEmailAddress': 'provider@example.com'} + ) + self.test_data_generator.put_default_privilege_record_in_provider_table() + + # Create message with adverseActionId (indicating an encumbrance was created) + message = self._generate_privilege_investigation_closed_message() + message['detail']['adverseActionId'] = str(uuid4()) + event = self._create_sqs_event(message) + + # Execute the handler + result = privilege_investigation_closed_notification_listener(event, self.mock_context) + + # Should succeed with no batch failures + self.assertEqual({'batchItemFailures': []}, result) + + # Verify NO notifications were sent (encumbrance notification will handle it) + mock_state_email.assert_not_called() diff --git a/backend/compact-connect/lambdas/python/disaster-recovery/requirements-dev.txt b/backend/compact-connect/lambdas/python/disaster-recovery/requirements-dev.txt index 8c2dca592..050970754 100644 --- a/backend/compact-connect/lambdas/python/disaster-recovery/requirements-dev.txt +++ b/backend/compact-connect/lambdas/python/disaster-recovery/requirements-dev.txt @@ -1,27 +1,27 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/disaster-recovery/requirements-dev.in +# pip-compile --no-emit-index-url --no-strip-extras lambdas/python/disaster-recovery/requirements-dev.in # -boto3==1.40.35 +boto3==1.40.56 # via moto -botocore==1.40.35 +botocore==1.40.56 # via # boto3 # moto # s3transfer -certifi==2025.8.3 +certifi==2025.10.5 # via requests cffi==2.0.0 # via cryptography -charset-normalizer==3.4.3 +charset-normalizer==3.4.4 # via requests -cryptography==46.0.1 +cryptography==46.0.3 # via moto docker==7.1.0 # via moto -idna==3.10 +idna==3.11 # via requests jinja2==3.1.6 # via moto @@ -29,11 +29,11 @@ jmespath==1.0.1 # via # boto3 # botocore -markupsafe==3.0.2 +markupsafe==3.0.3 # via # jinja2 # werkzeug -moto[dynamodb,s3]==5.1.12 +moto[dynamodb,s3]==5.1.15 # via -r lambdas/python/disaster-recovery/requirements-dev.in py-partiql-parser==0.6.1 # via moto @@ -43,7 +43,7 @@ python-dateutil==2.9.0.post0 # via # botocore # moto -pyyaml==6.0.2 +pyyaml==6.0.3 # via # moto # responses diff --git a/backend/compact-connect/lambdas/python/disaster-recovery/requirements.txt b/backend/compact-connect/lambdas/python/disaster-recovery/requirements.txt index 800fc58fc..ffc3ccb09 100644 --- a/backend/compact-connect/lambdas/python/disaster-recovery/requirements.txt +++ b/backend/compact-connect/lambdas/python/disaster-recovery/requirements.txt @@ -1,6 +1,6 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/disaster-recovery/requirements.in +# pip-compile --no-emit-index-url --no-strip-extras lambdas/python/disaster-recovery/requirements.in # diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/bulk_upload.py b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/bulk_upload.py index 9aff60105..7c518bac3 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/bulk_upload.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/bulk_upload.py @@ -163,11 +163,11 @@ def process_bulk_upload_file( if duplicate_ssn_check_flag_enabled: matched_ssn_index = ssns_in_file_upload.get(license_ssn) if matched_ssn_index: - raise ValidationError( - message=f'Duplicate License SSN detected. SSN matches with record ' - f'{matched_ssn_index}. Every record must have a unique SSN within the same ' - f'file.' - ) + raise ValidationError( + message=f'Duplicate License SSN detected. SSN matches with record ' + f'{matched_ssn_index}. Every record must have a unique SSN within the same ' + f'file.' + ) ssns_in_file_upload.update({license_ssn: i + 1}) except TypeError as e: # This will be raised, if `raw_license` includes compact and/or jurisdiction fields diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/encumbrance.py b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/encumbrance.py index 652a861b4..ced4bcc95 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/encumbrance.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/encumbrance.py @@ -1,5 +1,5 @@ import json -from uuid import uuid4 +from uuid import UUID, uuid4 from aws_lambda_powertools.utilities.typing import LambdaContext from cc_common.config import config, logger @@ -16,7 +16,7 @@ ) from cc_common.exceptions import CCInvalidRequestException from cc_common.license_util import LicenseUtility -from cc_common.utils import api_handler, authorize_state_level_only_action +from cc_common.utils import api_handler, authorize_state_level_only_action, to_uuid from marshmallow import ValidationError PRIVILEGE_ENCUMBRANCE_ENDPOINT_RESOURCE = ( @@ -54,30 +54,29 @@ def encumbrance_handler(event: dict, context: LambdaContext) -> dict: raise CCInvalidRequestException('Invalid endpoint requested') -def _get_submitting_user_id(event: dict): - return event['requestContext']['authorizer']['claims']['sub'] - - -def _generate_adverse_action_for_record_type( - event: dict, adverse_action_against_record_type: AdverseActionAgainstEnum -) -> AdverseActionData: - # get the compact, providerId, jurisdiction, licenseType from the path parameters - compact = event['pathParameters']['compact'] - provider_id = event['pathParameters']['providerId'] - jurisdiction = event['pathParameters']['jurisdiction'] - # the path parameter says licenseType, but it's actually the license type abbreviation - license_type_abbreviation_from_path_parameter = event['pathParameters']['licenseType'].lower() - - body = json.loads(event['body']) - # validate the request body +def _load_adverse_action_post_body(event: dict) -> dict: try: schema = AdverseActionPostRequestSchema() - adverse_action_request = schema.load(body) + return schema.loads(event['body']) except ValidationError as e: raise CCInvalidRequestException(f'Invalid request body: {e.messages}') from e + +def _get_submitting_user_id(event: dict) -> str: + return event['requestContext']['authorizer']['claims']['sub'] + + +def _generate_adverse_action_for_record_type( + compact: str, + provider_id: UUID, + jurisdiction: str, + license_type_abbr: str, + submitting_user: str, + adverse_action_post_body: dict, + adverse_action_against_record_type: AdverseActionAgainstEnum, +) -> AdverseActionData: current_date = config.expiration_resolution_date - encumbrance_effective_date = adverse_action_request['encumbranceEffectiveDate'] + encumbrance_effective_date = adverse_action_post_body['encumbranceEffectiveDate'] if encumbrance_effective_date > current_date: raise CCInvalidRequestException('The encumbrance date must not be a future date') @@ -88,53 +87,65 @@ def _generate_adverse_action_for_record_type( adverse_action.providerId = provider_id adverse_action.jurisdiction = jurisdiction - license_type = LicenseUtility.get_license_type_by_abbreviation( - compact=compact, abbreviation=license_type_abbreviation_from_path_parameter - ) + license_type = LicenseUtility.get_license_type_by_abbreviation(compact=compact, abbreviation=license_type_abbr) if not license_type: raise CCInvalidRequestException( f'Could not find license type information based on provided parameters ' - f"compact: '{compact}' licenseType: '{license_type_abbreviation_from_path_parameter}'" + f"compact: '{compact}' licenseType: '{license_type_abbr}'" ) adverse_action.licenseTypeAbbreviation = license_type.abbreviation adverse_action.licenseType = license_type.name adverse_action.actionAgainst = adverse_action_against_record_type - adverse_action.encumbranceType = EncumbranceType(adverse_action_request['encumbranceType']) + adverse_action.encumbranceType = EncumbranceType(adverse_action_post_body['encumbranceType']) # TODO - remove the flag conditions as part of https://github.com/csg-org/CompactConnect/issues/1136 # noqa: FIX002 from cc_common.feature_flag_client import FeatureFlagEnum, is_feature_enabled if is_feature_enabled(FeatureFlagEnum.ENCUMBRANCE_MULTI_CATEGORY_FLAG): - if 'clinicalPrivilegeActionCategory' in adverse_action_request: + if 'clinicalPrivilegeActionCategory' in adverse_action_post_body: # replicate data to both the deprecated and new fields adverse_action.clinicalPrivilegeActionCategory = ClinicalPrivilegeActionCategory( - adverse_action_request['clinicalPrivilegeActionCategory'] + adverse_action_post_body['clinicalPrivilegeActionCategory'] ) adverse_action.clinicalPrivilegeActionCategories = [ - ClinicalPrivilegeActionCategory(adverse_action_request['clinicalPrivilegeActionCategory']) + ClinicalPrivilegeActionCategory(adverse_action_post_body['clinicalPrivilegeActionCategory']) ] else: - adverse_action.clinicalPrivilegeActionCategories = adverse_action_request[ + adverse_action.clinicalPrivilegeActionCategories = adverse_action_post_body[ 'clinicalPrivilegeActionCategories' ] else: adverse_action.clinicalPrivilegeActionCategory = ClinicalPrivilegeActionCategory( - adverse_action_request['clinicalPrivilegeActionCategory'] + adverse_action_post_body['clinicalPrivilegeActionCategory'] ) adverse_action.effectiveStartDate = encumbrance_effective_date - adverse_action.submittingUser = _get_submitting_user_id(event) + adverse_action.submittingUser = submitting_user adverse_action.creationDate = config.current_standard_datetime adverse_action.adverseActionId = uuid4() return adverse_action -def handle_privilege_encumbrance(event: dict) -> dict: +def _create_privilege_encumbrance_internal( + compact: str, + jurisdiction: str, + provider_id: UUID, + license_type_abbr: str, + submitting_user: str, + adverse_action_post_body: dict, +) -> UUID: + """Internal handler for creating privilege encumbrances that returns the adverse action ID""" + logger.info('Processing adverse action updates for privilege record') adverse_action = _generate_adverse_action_for_record_type( - event=event, adverse_action_against_record_type=AdverseActionAgainstEnum.PRIVILEGE + compact=compact, + jurisdiction=jurisdiction, + provider_id=provider_id, + license_type_abbr=license_type_abbr, + adverse_action_post_body=adverse_action_post_body, + adverse_action_against_record_type=AdverseActionAgainstEnum.PRIVILEGE, + submitting_user=submitting_user, ) - logger.info('Processing adverse action updates for privilege record') config.data_client.encumber_privilege(adverse_action) # Publish privilege encumbrance event @@ -147,17 +158,49 @@ def handle_privilege_encumbrance(event: dict) -> dict: effective_date=adverse_action.effectiveStartDate, ) - return {'message': 'OK'} + return adverse_action.adverseActionId -def handle_license_encumbrance(event: dict) -> dict: - adverse_action = _generate_adverse_action_for_record_type( - event=event, adverse_action_against_record_type=AdverseActionAgainstEnum.LICENSE +def handle_privilege_encumbrance(event: dict) -> dict: + """Public API handler for creating privilege encumbrances""" + # Parse event parameters + compact = event['pathParameters']['compact'] + jurisdiction = event['pathParameters']['jurisdiction'] + provider_id = to_uuid(event['pathParameters']['providerId'], 'Invalid providerId provided') + license_type_abbr = event['pathParameters']['licenseType'].lower() + submitting_user = _get_submitting_user_id(event) + adverse_action_post_body = _load_adverse_action_post_body(event) + + _create_privilege_encumbrance_internal( + compact=compact, + jurisdiction=jurisdiction, + provider_id=provider_id, + license_type_abbr=license_type_abbr, + submitting_user=submitting_user, + adverse_action_post_body=adverse_action_post_body, ) - logger.info('Processing adverse action updates for license record') + return {'message': 'OK'} - adverse_action.serialize_to_database_record() +def _create_license_encumbrance_internal( + compact: str, + jurisdiction: str, + provider_id: UUID, + license_type_abbr: str, + submitting_user: str, + adverse_action_post_body: dict, +) -> UUID: + """Internal handler for creating license encumbrances that returns the adverse action ID""" + logger.info('Processing adverse action updates for license record') + adverse_action = _generate_adverse_action_for_record_type( + compact=compact, + jurisdiction=jurisdiction, + provider_id=provider_id, + license_type_abbr=license_type_abbr, + adverse_action_post_body=adverse_action_post_body, + adverse_action_against_record_type=AdverseActionAgainstEnum.LICENSE, + submitting_user=submitting_user, + ) config.data_client.encumber_license(adverse_action) # Publish license encumbrance event @@ -171,6 +214,27 @@ def handle_license_encumbrance(event: dict) -> dict: effective_date=adverse_action.effectiveStartDate, ) + return adverse_action.adverseActionId + + +def handle_license_encumbrance(event: dict) -> dict: + """Public API handler for creating license encumbrances""" + # Parse event parameters + compact = event['pathParameters']['compact'] + jurisdiction = event['pathParameters']['jurisdiction'] + provider_id = to_uuid(event['pathParameters']['providerId'], 'Invalid providerId provided') + license_type_abbr = event['pathParameters']['licenseType'].lower() + submitting_user = _get_submitting_user_id(event) + adverse_action_post_body = _load_adverse_action_post_body(event) + + _create_license_encumbrance_internal( + compact=compact, + jurisdiction=jurisdiction, + provider_id=provider_id, + license_type_abbr=license_type_abbr, + submitting_user=submitting_user, + adverse_action_post_body=adverse_action_post_body, + ) return {'message': 'OK'} @@ -184,10 +248,10 @@ def handle_privilege_encumbrance_lifting(event: dict) -> dict: # Extract path parameters compact = event['pathParameters']['compact'] - provider_id = event['pathParameters']['providerId'] + provider_id = to_uuid(event['pathParameters']['providerId'], 'Invalid providerId provided') jurisdiction = event['pathParameters']['jurisdiction'] license_type_abbreviation = event['pathParameters']['licenseType'].lower() - encumbrance_id = event['pathParameters']['encumbranceId'] + encumbrance_id = to_uuid(event['pathParameters']['encumbranceId'], 'Invalid encumbranceId provided') # Parse and validate request body body = json.loads(event['body']) @@ -223,7 +287,7 @@ def handle_privilege_encumbrance_lifting(event: dict) -> dict: effective_date=lift_date, ) - return {'message': 'OK'} + return {'message': 'OK'} def handle_license_encumbrance_lifting(event: dict) -> dict: @@ -236,10 +300,10 @@ def handle_license_encumbrance_lifting(event: dict) -> dict: # Extract path parameters compact = event['pathParameters']['compact'] - provider_id = event['pathParameters']['providerId'] + provider_id = to_uuid(event['pathParameters']['providerId'], 'Invalid providerId provided') jurisdiction = event['pathParameters']['jurisdiction'] license_type_abbreviation = event['pathParameters']['licenseType'].lower() - encumbrance_id = event['pathParameters']['encumbranceId'] + encumbrance_id = to_uuid(event['pathParameters']['encumbranceId'], 'Invalid encumbranceId provided') # Parse and validate request body body = json.loads(event['body']) @@ -275,4 +339,4 @@ def handle_license_encumbrance_lifting(event: dict) -> dict: effective_date=lift_date, ) - return {'message': 'OK'} + return {'message': 'OK'} diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/investigation.py b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/investigation.py new file mode 100644 index 000000000..9b3a7f4bb --- /dev/null +++ b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/investigation.py @@ -0,0 +1,281 @@ +import json +from uuid import UUID, uuid4 + +from aws_lambda_powertools.utilities.typing import LambdaContext +from cc_common.config import config, logger +from cc_common.data_model.schema.common import ( + CCPermissionsAction, + InvestigationAgainstEnum, +) +from cc_common.data_model.schema.investigation import InvestigationData +from cc_common.data_model.schema.investigation.api import ( + InvestigationPatchRequestSchema, +) +from cc_common.exceptions import CCInvalidRequestException +from cc_common.license_util import LicenseUtility +from cc_common.utils import api_handler, authorize_state_level_only_action, to_uuid +from marshmallow import ValidationError + +from .encumbrance import _create_license_encumbrance_internal, _create_privilege_encumbrance_internal + +PRIVILEGE_INVESTIGATION_ENDPOINT_RESOURCE = ( + '/v1/compacts/{compact}/providers/{providerId}/privileges/' + 'jurisdiction/{jurisdiction}/licenseType/{licenseType}/investigation' +) +LICENSE_INVESTIGATION_ENDPOINT_RESOURCE = ( + '/v1/compacts/{compact}/providers/{providerId}/licenses/' + 'jurisdiction/{jurisdiction}/licenseType/{licenseType}/investigation' +) +PRIVILEGE_INVESTIGATION_ID_ENDPOINT_RESOURCE = ( + '/v1/compacts/{compact}/providers/{providerId}/privileges/' + 'jurisdiction/{jurisdiction}/licenseType/{licenseType}/investigation/{investigationId}' +) +LICENSE_INVESTIGATION_ID_ENDPOINT_RESOURCE = ( + '/v1/compacts/{compact}/providers/{providerId}/licenses/' + 'jurisdiction/{jurisdiction}/licenseType/{licenseType}/investigation/{investigationId}' +) + + +@api_handler +@authorize_state_level_only_action(action=CCPermissionsAction.ADMIN) +def investigation_handler(event: dict, context: LambdaContext) -> dict: + """Investigation handler""" + # Get the cognito sub of the caller for tracing + cognito_sub = event['requestContext']['authorizer']['claims']['sub'] + + with logger.append_context_keys(aws_request=context.aws_request_id, cognito_sub=cognito_sub): + if event['httpMethod'] == 'POST' and event['resource'] == PRIVILEGE_INVESTIGATION_ENDPOINT_RESOURCE: + return handle_privilege_investigation(event) + if event['httpMethod'] == 'POST' and event['resource'] == LICENSE_INVESTIGATION_ENDPOINT_RESOURCE: + return handle_license_investigation(event) + if event['httpMethod'] == 'PATCH' and event['resource'] == PRIVILEGE_INVESTIGATION_ID_ENDPOINT_RESOURCE: + return handle_privilege_investigation_close(event) + if event['httpMethod'] == 'PATCH' and event['resource'] == LICENSE_INVESTIGATION_ID_ENDPOINT_RESOURCE: + return handle_license_investigation_close(event) + + raise CCInvalidRequestException('Invalid endpoint requested') + + +def _load_investigation_patch_body(event: dict) -> dict: + # Parse and validate request body + body = json.loads(event['body']) + try: + return InvestigationPatchRequestSchema().load(body) + except ValidationError as e: + raise CCInvalidRequestException(f'Invalid request body: {e.messages}') from e + + +def _generate_investigation_for_record_type( + compact: str, + jurisdiction: str, + provider_id: UUID, + license_type_abbr: str, + investigation_against_record_type: InvestigationAgainstEnum, + cognito_sub: str, +) -> InvestigationData: + license_type = LicenseUtility.get_license_type_by_abbreviation(compact=compact, abbreviation=license_type_abbr) + + if not license_type: + raise CCInvalidRequestException( + f'Could not find license type information based on provided parameters ' + f"compact: '{compact}' licenseType: '{license_type_abbr}'" + ) + + # populate the investigation data to be stored in the database + return InvestigationData.create_new( + { + 'compact': compact, + 'jurisdiction': jurisdiction, + 'providerId': provider_id, + 'investigationId': uuid4(), + 'licenseType': license_type.name, + 'investigationAgainst': investigation_against_record_type, + 'submittingUser': cognito_sub, + 'creationDate': config.current_standard_datetime, + } + ) + + +def handle_privilege_investigation(event: dict) -> dict: + """Public API handler for creating privilege investigations""" + # Parse event parameters + compact = event['pathParameters']['compact'] + jurisdiction = event['pathParameters']['jurisdiction'] + provider_id = to_uuid(event['pathParameters']['providerId'], 'Invalid providerId provided') + license_type_abbr = event['pathParameters']['licenseType'].lower() + cognito_sub = event['requestContext']['authorizer']['claims']['sub'] + + investigation = _generate_investigation_for_record_type( + compact=compact, + jurisdiction=jurisdiction, + provider_id=provider_id, + license_type_abbr=license_type_abbr, + investigation_against_record_type=InvestigationAgainstEnum.PRIVILEGE, + cognito_sub=cognito_sub, + ) + logger.info('Processing investigation updates for privilege record') + config.data_client.create_investigation(investigation) + + # Publish privilege investigation event + config.event_bus_client.publish_investigation_event( + source='org.compactconnect.provider-data', + compact=investigation.compact, + provider_id=investigation.providerId, + jurisdiction=investigation.jurisdiction, + create_date=investigation.creationDate, + license_type_abbreviation=investigation.licenseTypeAbbreviation, + investigation_against=InvestigationAgainstEnum.PRIVILEGE, + investigation_id=investigation.investigationId, + ) + + return {'message': 'OK'} + + +def handle_license_investigation(event: dict) -> dict: + """Public API handler for creating license investigations""" + # Parse event parameters + compact = event['pathParameters']['compact'] + jurisdiction = event['pathParameters']['jurisdiction'] + provider_id = to_uuid(event['pathParameters']['providerId'], 'Invalid providerId provided') + license_type_abbr = event['pathParameters']['licenseType'].lower() + cognito_sub = event['requestContext']['authorizer']['claims']['sub'] + + investigation = _generate_investigation_for_record_type( + compact=compact, + jurisdiction=jurisdiction, + provider_id=provider_id, + license_type_abbr=license_type_abbr, + investigation_against_record_type=InvestigationAgainstEnum.LICENSE, + cognito_sub=cognito_sub, + ) + logger.info('Processing investigation updates for license record') + config.data_client.create_investigation(investigation) + + # Publish license investigation event + config.event_bus_client.publish_investigation_event( + source='org.compactconnect.provider-data', + compact=investigation.compact, + provider_id=investigation.providerId, + jurisdiction=investigation.jurisdiction, + create_date=investigation.creationDate, + license_type_abbreviation=investigation.licenseTypeAbbreviation, + investigation_against=InvestigationAgainstEnum.LICENSE, + investigation_id=investigation.investigationId, + ) + + return {'message': 'OK'} + + +def handle_privilege_investigation_close(event: dict) -> dict: + """Handle closing investigation for a privilege record""" + # Parse event parameters + compact = event['pathParameters']['compact'] + jurisdiction = event['pathParameters']['jurisdiction'] + provider_id = to_uuid(event['pathParameters']['providerId'], 'Invalid providerId provided') + license_type_abbr = event['pathParameters']['licenseType'].lower() + investigation_id = to_uuid(event['pathParameters']['investigationId'], 'Invalid investigationId provided') + cognito_sub = event['requestContext']['authorizer']['claims']['sub'] + investigation_patch_body = _load_investigation_patch_body(event) + + logger.info('Processing privilege investigation closure') + now = config.current_standard_datetime + + # Create encumbrance if provided + resulting_encumbrance_id = None + encumbrance_data = investigation_patch_body.get('encumbrance') + if encumbrance_data: + # Create the encumbrance the same way we do directly via the encumbrance endpoint + resulting_encumbrance_id = _create_privilege_encumbrance_internal( + compact=compact, + jurisdiction=jurisdiction, + provider_id=provider_id, + license_type_abbr=license_type_abbr, + submitting_user=cognito_sub, + adverse_action_post_body=encumbrance_data, + ) + + # Call the data client method to close the investigation + config.data_client.close_investigation( + compact=compact, + provider_id=provider_id, + jurisdiction=jurisdiction, + license_type_abbreviation=license_type_abbr, + investigation_id=investigation_id, + closing_user=cognito_sub, + close_date=now, + investigation_against=InvestigationAgainstEnum.PRIVILEGE, + resulting_encumbrance_id=resulting_encumbrance_id, + ) + + # Publish privilege investigation closure event + config.event_bus_client.publish_investigation_closed_event( + source='org.compactconnect.provider-data', + compact=compact, + provider_id=provider_id, + jurisdiction=jurisdiction, + license_type_abbreviation=license_type_abbr, + close_date=now, + investigation_against=InvestigationAgainstEnum.PRIVILEGE, + investigation_id=investigation_id, + adverse_action_id=resulting_encumbrance_id, + ) + + return {'message': 'OK'} + + +def handle_license_investigation_close(event: dict) -> dict: + """Handle closing investigation for a license record""" + # Parse event parameters + compact = event['pathParameters']['compact'] + jurisdiction = event['pathParameters']['jurisdiction'] + provider_id = to_uuid(event['pathParameters']['providerId'], 'Invalid providerId provided') + license_type_abbr = event['pathParameters']['licenseType'].lower() + investigation_id = to_uuid(event['pathParameters']['investigationId'], 'Invalid investigationId provided') + cognito_sub = event['requestContext']['authorizer']['claims']['sub'] + investigation_patch_body = _load_investigation_patch_body(event) + + logger.info('Processing license investigation closure') + + now = config.current_standard_datetime + + # Create encumbrance if provided + resulting_encumbrance_id = None + encumbrance_data = investigation_patch_body.get('encumbrance') + if encumbrance_data: + # Create the encumbrance the same way we do directly via the encumbrance endpoint + resulting_encumbrance_id = _create_license_encumbrance_internal( + compact=compact, + jurisdiction=jurisdiction, + provider_id=provider_id, + license_type_abbr=license_type_abbr, + submitting_user=cognito_sub, + adverse_action_post_body=encumbrance_data, + ) + + # Call the data client method to close the investigation + config.data_client.close_investigation( + compact=compact, + provider_id=provider_id, + jurisdiction=jurisdiction, + license_type_abbreviation=license_type_abbr, + investigation_id=investigation_id, + closing_user=cognito_sub, + close_date=now, + investigation_against=InvestigationAgainstEnum.LICENSE, + resulting_encumbrance_id=resulting_encumbrance_id, + ) + + # Publish license investigation closure event + config.event_bus_client.publish_investigation_closed_event( + source='org.compactconnect.provider-data', + compact=compact, + provider_id=provider_id, + jurisdiction=jurisdiction, + license_type_abbreviation=license_type_abbr, + close_date=now, + investigation_against=InvestigationAgainstEnum.LICENSE, + investigation_id=investigation_id, + adverse_action_id=resulting_encumbrance_id, + ) + + return {'message': 'OK'} diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/requirements-dev.in b/backend/compact-connect/lambdas/python/provider-data-v1/requirements-dev.in index 804353503..1b04a07e0 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/requirements-dev.in +++ b/backend/compact-connect/lambdas/python/provider-data-v1/requirements-dev.in @@ -1,2 +1,2 @@ moto[dynamodb, s3]>=5.0.12, <6 -Faker>=28,<29 +Faker>=37, <38 diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/requirements-dev.txt b/backend/compact-connect/lambdas/python/provider-data-v1/requirements-dev.txt index 4c01da874..62531f73c 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/requirements-dev.txt +++ b/backend/compact-connect/lambdas/python/provider-data-v1/requirements-dev.txt @@ -1,29 +1,29 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/provider-data-v1/requirements-dev.in +# pip-compile --no-emit-index-url --no-strip-extras lambdas/python/provider-data-v1/requirements-dev.in # -boto3==1.40.35 +boto3==1.40.56 # via moto -botocore==1.40.35 +botocore==1.40.56 # via # boto3 # moto # s3transfer -certifi==2025.8.3 +certifi==2025.10.5 # via requests cffi==2.0.0 # via cryptography -charset-normalizer==3.4.3 +charset-normalizer==3.4.4 # via requests -cryptography==46.0.1 +cryptography==46.0.3 # via moto docker==7.1.0 # via moto faker==28.4.1 # via -r lambdas/python/provider-data-v1/requirements-dev.in -idna==3.10 +idna==3.11 # via requests jinja2==3.1.6 # via moto @@ -31,11 +31,11 @@ jmespath==1.0.1 # via # boto3 # botocore -markupsafe==3.0.2 +markupsafe==3.0.3 # via # jinja2 # werkzeug -moto[dynamodb,s3]==5.1.12 +moto[dynamodb,s3]==5.1.15 # via -r lambdas/python/provider-data-v1/requirements-dev.in py-partiql-parser==0.6.1 # via moto @@ -46,7 +46,7 @@ python-dateutil==2.9.0.post0 # botocore # faker # moto -pyyaml==6.0.2 +pyyaml==6.0.3 # via # moto # responses diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/requirements.txt b/backend/compact-connect/lambdas/python/provider-data-v1/requirements.txt index 15e52fd4e..5060b3544 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/requirements.txt +++ b/backend/compact-connect/lambdas/python/provider-data-v1/requirements.txt @@ -1,6 +1,6 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/provider-data-v1/requirements.in +# pip-compile --no-emit-index-url --no-strip-extras lambdas/python/provider-data-v1/requirements.in # diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/__init__.py b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/__init__.py index 7ffe1cbbe..fe0ac38f7 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/__init__.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/__init__.py @@ -22,6 +22,13 @@ class TstFunction(TstLambdas): """Base class to set up Moto mocking and create mock AWS resources for functional testing""" + def assertDictPartialMatch(self, expected: dict, actual: dict): # noqa: N802 emulating TestCase style here + for key, value in expected.items(): + try: + self.assertEqual(value, actual[key], f'Expected {key}: {value} but got {key}: {actual[key]}') + except KeyError: + self.fail(f'Missing expected key, {key}') + def setUp(self): # noqa: N801 invalid-name super().setUp() diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py index 911bfc774..ed11c68ea 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py @@ -1,6 +1,7 @@ import json from datetime import UTC, date, datetime, timedelta from unittest.mock import MagicMock, patch +from uuid import uuid4 from boto3.dynamodb.conditions import Key from cc_common.exceptions import CCInternalException @@ -71,7 +72,7 @@ def _when_testing_privilege_encumbrance(self, body_overrides: dict | None = None 'resource': PRIVILEGE_ENCUMBRANCE_ENDPOINT_RESOURCE, 'pathParameters': { 'compact': test_privilege_record.compact, - 'providerId': test_privilege_record.providerId, + 'providerId': str(test_privilege_record.providerId), 'jurisdiction': test_privilege_record.jurisdiction, 'licenseType': self.test_data_generator.get_license_type_abbr_for_license_type( compact=test_privilege_record.compact, license_type=test_privilege_record.licenseType @@ -446,7 +447,7 @@ def _when_testing_valid_license_encumbrance(self, body_overrides: dict | None = 'resource': LICENSE_ENCUMBRANCE_ENDPOINT_RESOURCE, 'pathParameters': { 'compact': test_license_record.compact, - 'providerId': test_license_record.providerId, + 'providerId': str(test_license_record.providerId), 'jurisdiction': test_license_record.jurisdiction, 'licenseType': self.test_data_generator.get_license_type_abbr_for_license_type( compact=test_license_record.compact, license_type=test_license_record.licenseType @@ -805,9 +806,9 @@ def test_should_raise_cc_not_found_exception_if_adverse_action_not_found(self): privilege_record, _ = self._setup_privilege_with_adverse_action() - # Use a non-existent adverse action ID + # Use a non-existent adverse action ID (valid UUID format that doesn't exist) event = self._generate_lift_encumbrance_event( - privilege_record, type('MockAdverseAction', (), {'adverseActionId': 'non-existent-id'})() + privilege_record, type('MockAdverseAction', (), {'adverseActionId': str(uuid4())})() ) response = encumbrance_handler(event, self.mock_context) @@ -1170,9 +1171,9 @@ def test_should_raise_cc_not_found_exception_if_adverse_action_not_found(self): license_record, _ = self._setup_license_with_adverse_action() - # Use a non-existent adverse action ID + # Use a non-existent adverse action ID (valid UUID format that doesn't exist) event = self._generate_lift_encumbrance_event( - license_record, type('MockAdverseAction', (), {'adverseActionId': 'non-existent-id'})() + license_record, type('MockAdverseAction', (), {'adverseActionId': str(uuid4())})() ) response = encumbrance_handler(event, self.mock_context) diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_investigation.py b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_investigation.py new file mode 100644 index 000000000..347361ac0 --- /dev/null +++ b/backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_investigation.py @@ -0,0 +1,1276 @@ +import json +from datetime import datetime +from unittest.mock import patch +from uuid import UUID + +from common_test.test_constants import ( + DEFAULT_AA_SUBMITTING_USER_ID, + DEFAULT_DATE_OF_UPDATE_TIMESTAMP, +) +from moto import mock_aws + +from .. import TstFunction + +PRIVILEGE_INVESTIGATION_ENDPOINT_RESOURCE = ( + '/v1/compacts/{compact}/providers/{providerId}/privileges/' + 'jurisdiction/{jurisdiction}/licenseType/{licenseType}/investigation' +) +LICENSE_INVESTIGATION_ENDPOINT_RESOURCE = ( + '/v1/compacts/{compact}/providers/{providerId}/licenses/' + 'jurisdiction/{jurisdiction}/licenseType/{licenseType}/investigation' +) +PRIVILEGE_INVESTIGATION_ID_ENDPOINT_RESOURCE = ( + '/v1/compacts/{compact}/providers/{providerId}/privileges/' + 'jurisdiction/{jurisdiction}/licenseType/{licenseType}/investigation/{investigationId}' +) +LICENSE_INVESTIGATION_ID_ENDPOINT_RESOURCE = ( + '/v1/compacts/{compact}/providers/{providerId}/licenses/' + 'jurisdiction/{jurisdiction}/licenseType/{licenseType}/investigation/{investigationId}' +) + +TEST_INVESTIGATION_START_DATE = '2023-01-15' +TEST_INVESTIGATION_CLOSE_DATE = '2023-02-15' +TEST_ENCUMBRANCE_EFFECTIVE_DATE = '2023-01-15' + + +def _generate_test_investigation_close_with_encumbrance_body(): + from cc_common.data_model.schema.common import ClinicalPrivilegeActionCategory, EncumbranceType + + return { + 'encumbrance': { + 'encumbranceEffectiveDate': TEST_ENCUMBRANCE_EFFECTIVE_DATE, + # These Enums are expected to be `str` type, so we'll directly access their .value + 'encumbranceType': EncumbranceType.SUSPENSION.value, + 'clinicalPrivilegeActionCategory': ClinicalPrivilegeActionCategory.UNSAFE_PRACTICE.value, + }, + } + + +@mock_aws +@patch('cc_common.config._Config.current_standard_datetime', datetime.fromisoformat(DEFAULT_DATE_OF_UPDATE_TIMESTAMP)) +class TestPostPrivilegeInvestigation(TstFunction): + """Test suite for privilege investigation endpoints.""" + + def _load_privilege_data(self): + """Load privilege test data from JSON file""" + + # Load provider record first (needed for encumbrance creation) + self.test_data_generator.put_default_provider_record_in_provider_table() + privilege = self.test_data_generator.generate_default_privilege() + self.test_data_generator.store_record_in_provider_table(privilege.serialize_to_database_record()) + return privilege + + def _when_testing_privilege_investigation(self): + test_privilege_record = self._load_privilege_data() + + test_event = self.test_data_generator.generate_test_api_event( + sub_override=DEFAULT_AA_SUBMITTING_USER_ID, + scope_override=f'openid email {test_privilege_record.jurisdiction}/aslp.admin', + value_overrides={ + 'httpMethod': 'POST', + 'resource': PRIVILEGE_INVESTIGATION_ENDPOINT_RESOURCE, + 'pathParameters': { + 'compact': test_privilege_record.compact, + 'providerId': str(test_privilege_record.providerId), + 'jurisdiction': test_privilege_record.jurisdiction, + 'licenseType': test_privilege_record.licenseTypeAbbreviation, + }, + }, + ) + + # return both the test event and the test privilege record + return test_event, test_privilege_record + + @patch('cc_common.event_bus_client.EventBusClient._publish_event') + def test_privilege_investigation_handler(self, mock_publish_event): + from cc_common.data_model.schema.common import InvestigationStatusEnum + from handlers.investigation import investigation_handler + from handlers.providers import get_provider + + event, test_privilege_record = self._when_testing_privilege_investigation() + + response = investigation_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, + ) + + # Verify that the investigation record was added to the provider data table + provider_user_records = self.config.data_client.get_provider_user_records( + compact=test_privilege_record.compact, + provider_id=test_privilege_record.providerId, + ) + investigation_records = provider_user_records.get_investigation_records_for_privilege( + privilege_jurisdiction=test_privilege_record.jurisdiction, + privilege_license_type_abbreviation=test_privilege_record.licenseTypeAbbreviation, + ) + self.assertEqual(1, len(investigation_records)) + investigation = investigation_records[0] + + # Verify the investigation record fields + expected_investigation = { + 'type': 'investigation', + 'compact': test_privilege_record.compact, + 'providerId': test_privilege_record.providerId, + 'jurisdiction': test_privilege_record.jurisdiction, + 'licenseType': test_privilege_record.licenseType, + 'investigationAgainst': 'privilege', + 'submittingUser': UUID(DEFAULT_AA_SUBMITTING_USER_ID), + 'creationDate': datetime.fromisoformat(DEFAULT_DATE_OF_UPDATE_TIMESTAMP), + 'dateOfUpdate': datetime.fromisoformat(DEFAULT_DATE_OF_UPDATE_TIMESTAMP), + 'investigationId': investigation.investigationId, + } + self.assertEqual(expected_investigation, investigation.to_dict()) + + # Verify that the privilege record was updated to be under investigation + updated_privilege_record = provider_user_records.get_privilege_records()[0] + + self.assertEqual(InvestigationStatusEnum.UNDER_INVESTIGATION, updated_privilege_record.investigationStatus) + + # Verify that investigation objects are included in the API response + api_event = self.test_data_generator.generate_test_api_event( + scope_override=f'openid email {test_privilege_record.jurisdiction}/aslp.readGeneral', + value_overrides={ + 'httpMethod': 'GET', + 'resource': '/v1/compacts/{compact}/providers/{providerId}', + 'pathParameters': { + 'compact': test_privilege_record.compact, + 'providerId': str(test_privilege_record.providerId), + }, + }, + ) + + api_response = get_provider(api_event, self.mock_context) + self.assertEqual(200, api_response['statusCode']) + + provider_data = json.loads(api_response['body']) + + # Verify that the privilege has investigation objects + privilege = provider_data['privileges'][0] + + expected_privilege = { + 'providerId': str(test_privilege_record.providerId), + 'investigationStatus': 'underInvestigation', + 'investigations': [ + { + 'type': 'investigation', + 'compact': test_privilege_record.compact, + 'providerId': str(test_privilege_record.providerId), + 'jurisdiction': test_privilege_record.jurisdiction, + 'licenseType': test_privilege_record.licenseType, + 'submittingUser': DEFAULT_AA_SUBMITTING_USER_ID, + 'creationDate': DEFAULT_DATE_OF_UPDATE_TIMESTAMP, + 'dateOfUpdate': DEFAULT_DATE_OF_UPDATE_TIMESTAMP, + 'investigationId': privilege['investigations'][0]['investigationId'], # Dynamic field + } + ], + } + + self.assertDictPartialMatch(expected_privilege, privilege) + + # Verify event was published with correct details + mock_publish_event.assert_called_once() + call_args = mock_publish_event.call_args[1] + + expected_event_args = { + 'source': 'org.compactconnect.provider-data', + 'detail_type': 'privilege.investigation', + 'event_batch_writer': None, + 'detail': { + 'compact': test_privilege_record.compact, + 'providerId': str(test_privilege_record.providerId), + 'jurisdiction': test_privilege_record.jurisdiction, + 'licenseTypeAbbreviation': test_privilege_record.licenseTypeAbbreviation, + 'eventTime': DEFAULT_DATE_OF_UPDATE_TIMESTAMP, + 'investigationAgainst': 'privilege', + 'investigationId': call_args['detail']['investigationId'], # Dynamic field + }, + } + self.assertEqual(expected_event_args, call_args) + + def test_privilege_investigation_handler_returns_access_denied_if_compact_admin(self): + """Verifying that only state admins are allowed to create privilege investigations""" + from handlers.investigation import investigation_handler + + event, test_privilege_record = self._when_testing_privilege_investigation() + + event['requestContext']['authorizer']['claims']['scope'] = f'openid email {test_privilege_record.compact}/admin' + + response = investigation_handler(event, self.mock_context) + self.assertEqual(403, response['statusCode'], msg=json.loads(response['body'])) + response_body = json.loads(response['body']) + + self.assertEqual( + {'message': 'Access denied'}, + response_body, + ) + + @patch('cc_common.event_bus_client.EventBusClient._publish_event') + def test_privilege_investigation_handler_handles_event_publishing_failure(self, mock_publish_event): + """Test that privilege investigation handler fails when event publishing fails.""" + from handlers.investigation import investigation_handler + + event, _ = self._when_testing_privilege_investigation() + mock_publish_event.side_effect = Exception('Event publishing failed') + + with self.assertRaises(Exception) as context: + investigation_handler(event, self.mock_context) + self.assertEqual('Event publishing failed', str(context.exception)) + + +@mock_aws +@patch('cc_common.config._Config.current_standard_datetime', datetime.fromisoformat(DEFAULT_DATE_OF_UPDATE_TIMESTAMP)) +class TestPostLicenseInvestigation(TstFunction): + """Test suite for license investigation endpoints.""" + + def _load_license_data(self): + """Load license test data from JSON file""" + + # Load provider record first (needed for encumbrance creation) + self.test_data_generator.put_default_provider_record_in_provider_table() + license_data = self.test_data_generator.generate_default_license() + self.test_data_generator.store_record_in_provider_table(license_data.serialize_to_database_record()) + return license_data + + def _when_testing_valid_license_investigation(self, body_overrides: dict | None = None): + test_license_record = self._load_license_data() + test_body = {} + if body_overrides: + test_body.update(body_overrides) + + test_event = self.test_data_generator.generate_test_api_event( + sub_override=DEFAULT_AA_SUBMITTING_USER_ID, + scope_override=f'openid email {test_license_record.jurisdiction}/aslp.admin', + value_overrides={ + 'httpMethod': 'POST', + 'resource': LICENSE_INVESTIGATION_ENDPOINT_RESOURCE, + 'pathParameters': { + 'compact': test_license_record.compact, + 'providerId': str(test_license_record.providerId), + 'jurisdiction': test_license_record.jurisdiction, + 'licenseType': test_license_record.licenseTypeAbbreviation, + }, + 'body': json.dumps(test_body), + }, + ) + + # return both the event and test license record + return test_event, test_license_record + + @patch('cc_common.event_bus_client.EventBusClient._publish_event') + def test_license_investigation_handler(self, mock_publish_event): + from cc_common.data_model.schema.common import InvestigationStatusEnum + from handlers.investigation import investigation_handler + from handlers.providers import get_provider + + event, test_license_record = self._when_testing_valid_license_investigation() + + response = investigation_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, + ) + + # Verify that the investigation record was added to the provider data table + # Perform a query to list all investigations for the provider using the starts_with key condition + provider_user_records = self.config.data_client.get_provider_user_records( + compact=test_license_record.compact, + provider_id=test_license_record.providerId, + ) + investigation_records = provider_user_records.get_investigation_records_for_license( + license_jurisdiction=test_license_record.jurisdiction, + license_type_abbreviation=test_license_record.licenseTypeAbbreviation, + ) + self.assertEqual(1, len(investigation_records)) + investigation = investigation_records[0] + + # Verify the investigation record fields + expected_investigation = { + 'type': 'investigation', + 'compact': test_license_record.compact, + 'providerId': test_license_record.providerId, + 'jurisdiction': test_license_record.jurisdiction, + 'licenseType': test_license_record.licenseType, + 'investigationAgainst': 'license', + 'submittingUser': UUID(DEFAULT_AA_SUBMITTING_USER_ID), + 'creationDate': datetime.fromisoformat(DEFAULT_DATE_OF_UPDATE_TIMESTAMP), + 'dateOfUpdate': datetime.fromisoformat(DEFAULT_DATE_OF_UPDATE_TIMESTAMP), + 'investigationId': investigation.investigationId, + } + self.assertEqual(expected_investigation, investigation.to_dict()) + + # Verify that the license record was updated to be under investigation + updated_license_record = provider_user_records.get_license_records()[0] + + self.assertEqual(InvestigationStatusEnum.UNDER_INVESTIGATION, updated_license_record.investigationStatus) + + # Verify that investigation objects are included in the API response + api_event = self.test_data_generator.generate_test_api_event( + scope_override=f'openid email {test_license_record.jurisdiction}/aslp.readGeneral', + value_overrides={ + 'httpMethod': 'GET', + 'resource': '/v1/compacts/{compact}/providers/{providerId}', + 'pathParameters': { + 'compact': test_license_record.compact, + 'providerId': str(test_license_record.providerId), + }, + }, + ) + + api_response = get_provider(api_event, self.mock_context) + self.assertEqual(200, api_response['statusCode']) + + provider_data = json.loads(api_response['body']) + + # Verify that the license has investigation objects + license_obj = provider_data['licenses'][0] + investigation = license_obj['investigations'][0] + + expected_license = { + 'providerId': str(test_license_record.providerId), + 'investigationStatus': 'underInvestigation', + 'investigations': [ + { + 'type': 'investigation', + 'compact': test_license_record.compact, + 'providerId': str(test_license_record.providerId), + 'jurisdiction': test_license_record.jurisdiction, + 'licenseType': test_license_record.licenseType, + 'submittingUser': DEFAULT_AA_SUBMITTING_USER_ID, + 'creationDate': DEFAULT_DATE_OF_UPDATE_TIMESTAMP, + 'dateOfUpdate': DEFAULT_DATE_OF_UPDATE_TIMESTAMP, + 'investigationId': investigation['investigationId'], # Dynamic field + } + ], + } + + self.assertDictPartialMatch(expected_license, license_obj) + + # Verify event was published with correct details + mock_publish_event.assert_called_once() + call_args = mock_publish_event.call_args[1] + + expected_event_args = { + 'source': 'org.compactconnect.provider-data', + 'detail_type': 'license.investigation', + 'event_batch_writer': None, + 'detail': { + 'compact': test_license_record.compact, + 'providerId': str(test_license_record.providerId), + 'jurisdiction': test_license_record.jurisdiction, + 'licenseTypeAbbreviation': test_license_record.licenseTypeAbbreviation, + 'eventTime': DEFAULT_DATE_OF_UPDATE_TIMESTAMP, + 'investigationAgainst': 'license', + 'investigationId': call_args['detail']['investigationId'], # Dynamic field + }, + } + self.assertEqual(expected_event_args, call_args) + + def test_license_investigation_handler_returns_access_denied_if_compact_admin(self): + """Verifying that only state admins are allowed to create license investigations""" + from handlers.investigation import investigation_handler + + event, test_license_record = self._when_testing_valid_license_investigation() + + event['requestContext']['authorizer']['claims']['scope'] = f'openid email {test_license_record.compact}/admin' + + response = investigation_handler(event, self.mock_context) + self.assertEqual(403, response['statusCode'], msg=json.loads(response['body'])) + response_body = json.loads(response['body']) + + self.assertEqual( + {'message': 'Access denied'}, + response_body, + ) + + @patch('cc_common.event_bus_client.EventBusClient._publish_event') + def test_license_investigation_handler_handles_event_publishing_failure(self, mock_publish_event): + """Test that license investigation handler fails when event publishing fails.""" + from handlers.investigation import investigation_handler + + event, _ = self._when_testing_valid_license_investigation() + mock_publish_event.side_effect = Exception('Event publishing failed') + + with self.assertRaises(Exception) as context: + investigation_handler(event, self.mock_context) + self.assertEqual('Event publishing failed', str(context.exception)) + + +@mock_aws +@patch('cc_common.config._Config.current_standard_datetime', datetime.fromisoformat(DEFAULT_DATE_OF_UPDATE_TIMESTAMP)) +class TestPatchPrivilegeInvestigationClose(TstFunction): + """Test suite for privilege investigation close endpoints.""" + + def _load_privilege_data(self): + """Load privilege test data using test data generator""" + # Load provider record first (needed for encumbrance creation) + self.test_data_generator.put_default_provider_record_in_provider_table() + privilege = self.test_data_generator.generate_default_privilege() + self.test_data_generator.store_record_in_provider_table(privilege.serialize_to_database_record()) + return privilege + + def _when_testing_privilege_investigation_close(self, body_overrides: dict | None = None): + test_privilege_record = self._load_privilege_data() + test_body = {} + if body_overrides: + test_body.update(body_overrides) + + # First create an investigation + create_event = self.test_data_generator.generate_test_api_event( + sub_override=DEFAULT_AA_SUBMITTING_USER_ID, + scope_override=f'openid email {test_privilege_record.jurisdiction}/aslp.admin', + value_overrides={ + 'httpMethod': 'POST', + 'resource': PRIVILEGE_INVESTIGATION_ENDPOINT_RESOURCE, + 'pathParameters': { + 'compact': test_privilege_record.compact, + 'providerId': str(test_privilege_record.providerId), + 'jurisdiction': test_privilege_record.jurisdiction, + 'licenseType': test_privilege_record.licenseTypeAbbreviation, + }, + }, + ) + + from handlers.investigation import investigation_handler + + investigation_handler(create_event, self.mock_context) + + # Get the investigation ID using the data client + provider_user_records = self.config.data_client.get_provider_user_records( + compact=test_privilege_record.compact, + provider_id=test_privilege_record.providerId, + ) + investigation_records = provider_user_records.get_investigation_records_for_privilege( + privilege_jurisdiction=test_privilege_record.jurisdiction, + privilege_license_type_abbreviation=test_privilege_record.licenseTypeAbbreviation, + ) + self.assertEqual(1, len(investigation_records)) + investigation_id = investigation_records[0].investigationId + + # Now create the close event + test_event = self.test_data_generator.generate_test_api_event( + sub_override=DEFAULT_AA_SUBMITTING_USER_ID, + scope_override=f'openid email {test_privilege_record.jurisdiction}/aslp.admin', + value_overrides={ + 'httpMethod': 'PATCH', + 'resource': PRIVILEGE_INVESTIGATION_ID_ENDPOINT_RESOURCE, + 'pathParameters': { + 'compact': test_privilege_record.compact, + 'providerId': str(test_privilege_record.providerId), + 'jurisdiction': test_privilege_record.jurisdiction, + 'licenseType': test_privilege_record.licenseTypeAbbreviation, + 'investigationId': str(investigation_id), + }, + 'body': json.dumps(test_body), + }, + ) + + return test_event, test_privilege_record, investigation_id + + @patch('cc_common.event_bus_client.EventBusClient._publish_event') + def test_privilege_investigation_close_handler(self, mock_publish_event): + from handlers.investigation import investigation_handler + from handlers.providers import get_provider + + event, test_privilege_record, investigation_id = self._when_testing_privilege_investigation_close() + + response = investigation_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, + ) + + # Verify that the investigation record was updated + provider_user_records = self.config.data_client.get_provider_user_records( + compact=test_privilege_record.compact, + provider_id=test_privilege_record.providerId, + ) + # Get all investigation records (including closed ones) + all_investigations = provider_user_records.get_investigation_records_for_privilege( + privilege_jurisdiction=test_privilege_record.jurisdiction, + privilege_license_type_abbreviation=test_privilege_record.licenseTypeAbbreviation, + filter_condition=lambda inv: inv.investigationId == investigation_id, + include_closed=True, + ) + self.assertEqual(1, len(all_investigations)) + investigation = all_investigations[0] + + expected_investigation = { + 'type': 'investigation', + 'compact': test_privilege_record.compact, + 'providerId': test_privilege_record.providerId, + 'jurisdiction': test_privilege_record.jurisdiction, + 'licenseType': test_privilege_record.licenseType, + 'investigationAgainst': 'privilege', + 'investigationId': investigation_id, + 'submittingUser': UUID(DEFAULT_AA_SUBMITTING_USER_ID), + 'creationDate': datetime.fromisoformat(DEFAULT_DATE_OF_UPDATE_TIMESTAMP), + 'closeDate': datetime.fromisoformat(DEFAULT_DATE_OF_UPDATE_TIMESTAMP), + 'closingUser': UUID(DEFAULT_AA_SUBMITTING_USER_ID), + 'dateOfUpdate': datetime.fromisoformat(DEFAULT_DATE_OF_UPDATE_TIMESTAMP), + } + + self.assertEqual(expected_investigation, investigation.to_dict()) + + # Verify that the privilege record no longer has investigation status + updated_privilege_record = provider_user_records.get_privilege_records()[0] + + self.assertIsNone(updated_privilege_record.investigationStatus) + + # Verify that investigation objects are removed from the API response + api_event = self.test_data_generator.generate_test_api_event( + scope_override=f'openid email {test_privilege_record.jurisdiction}/aslp.readGeneral', + value_overrides={ + 'httpMethod': 'GET', + 'resource': '/v1/compacts/{compact}/providers/{providerId}', + 'pathParameters': { + 'compact': test_privilege_record.compact, + 'providerId': str(test_privilege_record.providerId), + }, + }, + ) + + api_response = get_provider(api_event, self.mock_context) + self.assertEqual(200, api_response['statusCode']) + + provider_data = json.loads(api_response['body']) + + # Verify that the privilege has no investigation objects + privilege = provider_data['privileges'][0] + expected_privilege = { + 'investigations': [], + } + + self.assertEqual(expected_privilege['investigations'], privilege['investigations']) + + # Verify event was published with correct details (should be called twice: creation + closure) + self.assertEqual(2, mock_publish_event.call_count) + call_args = mock_publish_event.call_args[1] + + expected_event_args = { + 'source': 'org.compactconnect.provider-data', + 'detail_type': 'privilege.investigationClosed', + 'event_batch_writer': None, + 'detail': { + 'compact': test_privilege_record.compact, + 'providerId': str(test_privilege_record.providerId), + 'jurisdiction': test_privilege_record.jurisdiction, + 'licenseTypeAbbreviation': test_privilege_record.licenseTypeAbbreviation, + 'eventTime': DEFAULT_DATE_OF_UPDATE_TIMESTAMP, + 'investigationAgainst': 'privilege', + 'investigationId': call_args['detail']['investigationId'], # Dynamic field + }, + } + self.assertEqual(expected_event_args, call_args) + + def test_privilege_investigation_close_with_encumbrance_creates_encumbrance(self): + from handlers.investigation import investigation_handler + + event, test_privilege_record, investigation_id = self._when_testing_privilege_investigation_close( + body_overrides=_generate_test_investigation_close_with_encumbrance_body() + ) + + response = investigation_handler(event, self.mock_context) + self.assertEqual(200, response['statusCode'], msg=json.loads(response['body'])) + + # Verify that an encumbrance was created + provider_user_records = self.config.data_client.get_provider_user_records( + compact=test_privilege_record.compact, + provider_id=test_privilege_record.providerId, + ) + encumbrance_records = provider_user_records.get_adverse_action_records_for_privilege( + privilege_jurisdiction=test_privilege_record.jurisdiction, + privilege_license_type_abbreviation=test_privilege_record.licenseTypeAbbreviation, + ) + self.assertEqual(1, len(encumbrance_records)) + + # Verify that the investigation record has the resulting encumbrance ID + all_investigations = provider_user_records.get_investigation_records_for_privilege( + privilege_jurisdiction=test_privilege_record.jurisdiction, + privilege_license_type_abbreviation=test_privilege_record.licenseTypeAbbreviation, + filter_condition=lambda inv: inv.investigationId == investigation_id, + include_closed=True, + ) + self.assertEqual(1, len(all_investigations)) + investigation = all_investigations[0] + + self.assertIsNotNone(investigation.resultingEncumbranceId) + + +@mock_aws +@patch('cc_common.config._Config.current_standard_datetime', datetime.fromisoformat(DEFAULT_DATE_OF_UPDATE_TIMESTAMP)) +class TestPatchLicenseInvestigationClose(TstFunction): + """Test suite for license investigation close endpoints.""" + + def _load_license_data(self): + """Load license test data using test data generator""" + # Load provider record first (needed for encumbrance creation) + self.test_data_generator.put_default_provider_record_in_provider_table() + license_data = self.test_data_generator.generate_default_license() + self.test_data_generator.store_record_in_provider_table(license_data.serialize_to_database_record()) + return license_data + + def _when_testing_license_investigation_close(self, body_overrides: dict | None = None): + test_license_record = self._load_license_data() + test_body = {} + if body_overrides: + test_body.update(body_overrides) + + # First create an investigation + create_event = self.test_data_generator.generate_test_api_event( + sub_override=DEFAULT_AA_SUBMITTING_USER_ID, + scope_override=f'openid email {test_license_record.jurisdiction}/aslp.admin', + value_overrides={ + 'httpMethod': 'POST', + 'resource': LICENSE_INVESTIGATION_ENDPOINT_RESOURCE, + 'pathParameters': { + 'compact': test_license_record.compact, + 'providerId': str(test_license_record.providerId), + 'jurisdiction': test_license_record.jurisdiction, + 'licenseType': test_license_record.licenseTypeAbbreviation, + }, + }, + ) + + from handlers.investigation import investigation_handler + + investigation_handler(create_event, self.mock_context) + + # Get the investigation ID using the data client + provider_user_records = self.config.data_client.get_provider_user_records( + compact=test_license_record.compact, + provider_id=test_license_record.providerId, + ) + investigation_records = provider_user_records.get_investigation_records_for_license( + license_jurisdiction=test_license_record.jurisdiction, + license_type_abbreviation=test_license_record.licenseTypeAbbreviation, + ) + self.assertEqual(1, len(investigation_records)) + investigation_id = investigation_records[0].investigationId + + # Now create the close event + test_event = self.test_data_generator.generate_test_api_event( + sub_override=DEFAULT_AA_SUBMITTING_USER_ID, + scope_override=f'openid email {test_license_record.jurisdiction}/aslp.admin', + value_overrides={ + 'httpMethod': 'PATCH', + 'resource': LICENSE_INVESTIGATION_ID_ENDPOINT_RESOURCE, + 'pathParameters': { + 'compact': test_license_record.compact, + 'providerId': str(test_license_record.providerId), + 'jurisdiction': test_license_record.jurisdiction, + 'licenseType': test_license_record.licenseTypeAbbreviation, + 'investigationId': str(investigation_id), + }, + 'body': json.dumps(test_body), + }, + ) + + return test_event, test_license_record, investigation_id + + @patch('cc_common.event_bus_client.EventBusClient._publish_event') + def test_license_investigation_close_handler(self, mock_publish_event): + from handlers.investigation import investigation_handler + from handlers.providers import get_provider + + event, test_license_record, investigation_id = self._when_testing_license_investigation_close() + + response = investigation_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, + ) + + # Verify that the investigation record was updated + provider_user_records = self.config.data_client.get_provider_user_records( + compact=test_license_record.compact, + provider_id=test_license_record.providerId, + ) + # Get all investigation records (including closed ones) + all_investigations = provider_user_records.get_investigation_records_for_license( + license_jurisdiction=test_license_record.jurisdiction, + license_type_abbreviation=test_license_record.licenseTypeAbbreviation, + filter_condition=lambda inv: inv.investigationId == investigation_id, + include_closed=True, + ) + self.assertEqual(1, len(all_investigations)) + investigation = all_investigations[0] + + expected_investigation = { + 'type': 'investigation', + 'compact': test_license_record.compact, + 'providerId': test_license_record.providerId, + 'jurisdiction': test_license_record.jurisdiction, + 'licenseType': test_license_record.licenseType, + 'investigationAgainst': 'license', + 'investigationId': investigation_id, + 'submittingUser': UUID(DEFAULT_AA_SUBMITTING_USER_ID), + 'creationDate': datetime.fromisoformat(DEFAULT_DATE_OF_UPDATE_TIMESTAMP), + 'closeDate': datetime.fromisoformat(DEFAULT_DATE_OF_UPDATE_TIMESTAMP), + 'closingUser': UUID(DEFAULT_AA_SUBMITTING_USER_ID), + 'dateOfUpdate': datetime.fromisoformat(DEFAULT_DATE_OF_UPDATE_TIMESTAMP), + } + + self.assertEqual(expected_investigation, investigation.to_dict()) + + # Verify that the license record no longer has investigation status + updated_license_record = provider_user_records.get_license_records()[0] + + self.assertIsNone(updated_license_record.investigationStatus) + + # Verify that investigation objects are removed from the API response + api_event = self.test_data_generator.generate_test_api_event( + scope_override=f'openid email {test_license_record.jurisdiction}/aslp.readGeneral', + value_overrides={ + 'httpMethod': 'GET', + 'resource': '/v1/compacts/{compact}/providers/{providerId}', + 'pathParameters': { + 'compact': test_license_record.compact, + 'providerId': str(test_license_record.providerId), + }, + }, + ) + + api_response = get_provider(api_event, self.mock_context) + self.assertEqual(200, api_response['statusCode']) + + provider_data = json.loads(api_response['body']) + + # Verify that the license has no investigation objects + license_obj = provider_data['licenses'][0] + expected_license = { + 'investigations': [], + } + + self.assertEqual(expected_license['investigations'], license_obj['investigations']) + + # Verify event was published with correct details (should be called twice: creation + closure) + self.assertEqual(2, mock_publish_event.call_count) + call_args = mock_publish_event.call_args[1] + + expected_event_args = { + 'source': 'org.compactconnect.provider-data', + 'detail_type': 'license.investigationClosed', + 'event_batch_writer': None, + 'detail': { + 'compact': test_license_record.compact, + 'providerId': str(test_license_record.providerId), + 'jurisdiction': test_license_record.jurisdiction, + 'licenseTypeAbbreviation': test_license_record.licenseTypeAbbreviation, + 'eventTime': DEFAULT_DATE_OF_UPDATE_TIMESTAMP, + 'investigationAgainst': 'license', + 'investigationId': call_args['detail']['investigationId'], # Dynamic field + }, + } + self.assertEqual(expected_event_args, call_args) + + def test_license_investigation_close_with_encumbrance_creates_encumbrance(self): + from handlers.investigation import investigation_handler + + event, test_license_record, investigation_id = self._when_testing_license_investigation_close( + body_overrides=_generate_test_investigation_close_with_encumbrance_body() + ) + + response = investigation_handler(event, self.mock_context) + self.assertEqual(200, response['statusCode'], msg=json.loads(response['body'])) + + # Verify that an encumbrance was created + provider_user_records = self.config.data_client.get_provider_user_records( + compact=test_license_record.compact, + provider_id=test_license_record.providerId, + ) + encumbrance_records = provider_user_records.get_adverse_action_records_for_license( + license_jurisdiction=test_license_record.jurisdiction, + license_type_abbreviation=test_license_record.licenseTypeAbbreviation, + ) + self.assertEqual(1, len(encumbrance_records)) + + # Verify that the investigation record has the resulting encumbrance ID + all_investigations = provider_user_records.get_investigation_records_for_license( + license_jurisdiction=test_license_record.jurisdiction, + license_type_abbreviation=test_license_record.licenseTypeAbbreviation, + filter_condition=lambda inv: inv.investigationId == investigation_id, + include_closed=True, + ) + self.assertEqual(1, len(all_investigations)) + investigation = all_investigations[0] + + self.assertIsNotNone(investigation.resultingEncumbranceId) + + +@mock_aws +@patch('cc_common.config._Config.current_standard_datetime', datetime.fromisoformat(DEFAULT_DATE_OF_UPDATE_TIMESTAMP)) +class TestMultipleSimultaneousPrivilegeInvestigations(TstFunction): + """Test suite for multiple simultaneous privilege investigations.""" + + def _load_privilege_data(self): + """Load privilege test data using test data generator""" + # Load provider record first + self.test_data_generator.put_default_provider_record_in_provider_table() + privilege = self.test_data_generator.generate_default_privilege() + self.test_data_generator.store_record_in_provider_table(privilege.serialize_to_database_record()) + return privilege + + @patch('cc_common.event_bus_client.EventBusClient._publish_event') + def test_closing_one_of_multiple_investigations_maintains_investigation_status(self, mock_publish_event): + """Test that closing one investigation while another is open maintains investigation status.""" + from cc_common.data_model.schema.common import InvestigationStatusEnum, UpdateCategory + from handlers.investigation import investigation_handler + from handlers.providers import get_provider + + test_privilege_record = self._load_privilege_data() + + # Create first investigation + first_investigation_event = self.test_data_generator.generate_test_api_event( + sub_override=DEFAULT_AA_SUBMITTING_USER_ID, + scope_override=f'openid email {test_privilege_record.jurisdiction}/aslp.admin', + value_overrides={ + 'httpMethod': 'POST', + 'resource': PRIVILEGE_INVESTIGATION_ENDPOINT_RESOURCE, + 'pathParameters': { + 'compact': test_privilege_record.compact, + 'providerId': str(test_privilege_record.providerId), + 'jurisdiction': test_privilege_record.jurisdiction, + 'licenseType': test_privilege_record.licenseTypeAbbreviation, + }, + }, + ) + + response = investigation_handler(first_investigation_event, self.mock_context) + self.assertEqual(200, response['statusCode']) + + # Get the first investigation ID + provider_user_records = self.config.data_client.get_provider_user_records( + compact=test_privilege_record.compact, + provider_id=test_privilege_record.providerId, + ) + investigation_records = provider_user_records.get_investigation_records_for_privilege( + privilege_jurisdiction=test_privilege_record.jurisdiction, + privilege_license_type_abbreviation=test_privilege_record.licenseTypeAbbreviation, + ) + self.assertEqual(1, len(investigation_records)) + first_investigation_id = investigation_records[0].investigationId + + # Create second investigation + second_investigation_event = self.test_data_generator.generate_test_api_event( + sub_override=DEFAULT_AA_SUBMITTING_USER_ID, + scope_override=f'openid email {test_privilege_record.jurisdiction}/aslp.admin', + value_overrides={ + 'httpMethod': 'POST', + 'resource': PRIVILEGE_INVESTIGATION_ENDPOINT_RESOURCE, + 'pathParameters': { + 'compact': test_privilege_record.compact, + 'providerId': str(test_privilege_record.providerId), + 'jurisdiction': test_privilege_record.jurisdiction, + 'licenseType': test_privilege_record.licenseTypeAbbreviation, + }, + }, + ) + + response = investigation_handler(second_investigation_event, self.mock_context) + self.assertEqual(200, response['statusCode']) + + # Get the second investigation ID + provider_user_records = self.config.data_client.get_provider_user_records( + compact=test_privilege_record.compact, + provider_id=test_privilege_record.providerId, + ) + investigation_records = provider_user_records.get_investigation_records_for_privilege( + privilege_jurisdiction=test_privilege_record.jurisdiction, + privilege_license_type_abbreviation=test_privilege_record.licenseTypeAbbreviation, + ) + self.assertEqual(2, len(investigation_records)) + second_investigation_id = [ + inv.investigationId for inv in investigation_records if inv.investigationId != first_investigation_id + ][0] + + # Close the second investigation + close_second_event = self.test_data_generator.generate_test_api_event( + sub_override=DEFAULT_AA_SUBMITTING_USER_ID, + scope_override=f'openid email {test_privilege_record.jurisdiction}/aslp.admin', + value_overrides={ + 'httpMethod': 'PATCH', + 'resource': PRIVILEGE_INVESTIGATION_ID_ENDPOINT_RESOURCE, + 'pathParameters': { + 'compact': test_privilege_record.compact, + 'providerId': str(test_privilege_record.providerId), + 'jurisdiction': test_privilege_record.jurisdiction, + 'licenseType': test_privilege_record.licenseTypeAbbreviation, + 'investigationId': str(second_investigation_id), + }, + 'body': json.dumps({}), + }, + ) + + response = investigation_handler(close_second_event, self.mock_context) + self.assertEqual(200, response['statusCode']) + + # Verify that the privilege record still shows under investigation + provider_user_records = self.config.data_client.get_provider_user_records( + compact=test_privilege_record.compact, + provider_id=test_privilege_record.providerId, + ) + updated_privilege_record = provider_user_records.get_privilege_records()[0] + + self.assertEqual( + InvestigationStatusEnum.UNDER_INVESTIGATION, + updated_privilege_record.investigationStatus, + ) + + # Verify that one investigation is still visible in the API response + api_event = self.test_data_generator.generate_test_api_event( + scope_override=f'openid email {test_privilege_record.jurisdiction}/aslp.readGeneral', + value_overrides={ + 'httpMethod': 'GET', + 'resource': '/v1/compacts/{compact}/providers/{providerId}', + 'pathParameters': { + 'compact': test_privilege_record.compact, + 'providerId': str(test_privilege_record.providerId), + }, + }, + ) + + api_response = get_provider(api_event, self.mock_context) + self.assertEqual(200, api_response['statusCode']) + + provider_data = json.loads(api_response['body']) + privilege = provider_data['privileges'][0] + + self.assertEqual(1, len(privilege['investigations'])) + self.assertEqual(str(first_investigation_id), privilege['investigations'][0]['investigationId']) + + # Verify that there are two INVESTIGATION update records + provider_user_records = self.config.data_client.get_provider_user_records( + compact=test_privilege_record.compact, + provider_id=test_privilege_record.providerId, + ) + update_records = provider_user_records.get_update_records_for_privilege( + jurisdiction=test_privilege_record.jurisdiction, license_type=test_privilege_record.licenseType + ) + + investigation_update_records = [ + record for record in update_records if record.updateType == UpdateCategory.INVESTIGATION + ] + self.assertEqual(2, len(investigation_update_records)) + + # Verify that there are no CLOSING_INVESTIGATION update records + closing_update_records = [ + record for record in update_records if record.updateType == UpdateCategory.CLOSING_INVESTIGATION + ] + self.assertEqual(0, len(closing_update_records)) + + # Verify that investigation closed event WAS published (should be 3 calls: 2 creation + 1 closure) + self.assertEqual(3, mock_publish_event.call_count) + call_types = [call[1]['detail_type'] for call in mock_publish_event.call_args_list] + self.assertEqual(2, call_types.count('privilege.investigation')) + self.assertEqual(1, call_types.count('privilege.investigationClosed')) + + # Now close the first investigation + close_first_event = self.test_data_generator.generate_test_api_event( + sub_override=DEFAULT_AA_SUBMITTING_USER_ID, + scope_override=f'openid email {test_privilege_record.jurisdiction}/aslp.admin', + value_overrides={ + 'httpMethod': 'PATCH', + 'resource': PRIVILEGE_INVESTIGATION_ID_ENDPOINT_RESOURCE, + 'pathParameters': { + 'compact': test_privilege_record.compact, + 'providerId': str(test_privilege_record.providerId), + 'jurisdiction': test_privilege_record.jurisdiction, + 'licenseType': test_privilege_record.licenseTypeAbbreviation, + 'investigationId': str(first_investigation_id), + }, + 'body': json.dumps({}), + }, + ) + + response = investigation_handler(close_first_event, self.mock_context) + self.assertEqual(200, response['statusCode']) + + # Verify that the privilege record no longer has investigation status + provider_user_records = self.config.data_client.get_provider_user_records( + compact=test_privilege_record.compact, + provider_id=test_privilege_record.providerId, + ) + updated_privilege_record = provider_user_records.get_privilege_records()[0] + + self.assertIsNone(updated_privilege_record.investigationStatus) + + # Verify that there are no investigations visible in the API response + api_response = get_provider(api_event, self.mock_context) + self.assertEqual(200, api_response['statusCode']) + + provider_data = json.loads(api_response['body']) + privilege = provider_data['privileges'][0] + + self.assertEqual(0, len(privilege['investigations'])) + + # Verify that there are still two INVESTIGATION update records + provider_user_records = self.config.data_client.get_provider_user_records( + compact=test_privilege_record.compact, + provider_id=test_privilege_record.providerId, + ) + update_records = provider_user_records.get_update_records_for_privilege( + jurisdiction=test_privilege_record.jurisdiction, license_type=test_privilege_record.licenseType + ) + + investigation_update_records = [ + record for record in update_records if record.updateType == UpdateCategory.INVESTIGATION + ] + self.assertEqual(2, len(investigation_update_records)) + + # Verify that there is one CLOSING_INVESTIGATION update record + closing_update_records = [ + record for record in update_records if record.updateType == UpdateCategory.CLOSING_INVESTIGATION + ] + self.assertEqual(1, len(closing_update_records)) + + # Verify that investigation closed events were published (should be 4 calls total: 2 creation + 2 closure) + self.assertEqual(4, mock_publish_event.call_count) + call_types = [call[1]['detail_type'] for call in mock_publish_event.call_args_list] + self.assertEqual(2, call_types.count('privilege.investigation')) + self.assertEqual(2, call_types.count('privilege.investigationClosed')) + + +@mock_aws +@patch('cc_common.config._Config.current_standard_datetime', datetime.fromisoformat(DEFAULT_DATE_OF_UPDATE_TIMESTAMP)) +class TestMultipleSimultaneousLicenseInvestigations(TstFunction): + """Test suite for multiple simultaneous license investigations.""" + + def _load_license_data(self): + """Load license test data using test data generator""" + # Load provider record first + self.test_data_generator.put_default_provider_record_in_provider_table() + license_data = self.test_data_generator.generate_default_license() + self.test_data_generator.store_record_in_provider_table(license_data.serialize_to_database_record()) + return license_data + + @patch('cc_common.event_bus_client.EventBusClient._publish_event') + def test_closing_one_of_multiple_investigations_maintains_investigation_status(self, mock_publish_event): + """Test that closing one investigation while another is open maintains investigation status.""" + from cc_common.data_model.schema.common import InvestigationStatusEnum, UpdateCategory + from handlers.investigation import investigation_handler + from handlers.providers import get_provider + + test_license_record = self._load_license_data() + + # Create first investigation + first_investigation_event = self.test_data_generator.generate_test_api_event( + sub_override=DEFAULT_AA_SUBMITTING_USER_ID, + scope_override=f'openid email {test_license_record.jurisdiction}/aslp.admin', + value_overrides={ + 'httpMethod': 'POST', + 'resource': LICENSE_INVESTIGATION_ENDPOINT_RESOURCE, + 'pathParameters': { + 'compact': test_license_record.compact, + 'providerId': str(test_license_record.providerId), + 'jurisdiction': test_license_record.jurisdiction, + 'licenseType': test_license_record.licenseTypeAbbreviation, + }, + }, + ) + + response = investigation_handler(first_investigation_event, self.mock_context) + self.assertEqual(200, response['statusCode']) + + # Get the first investigation ID + provider_user_records = self.config.data_client.get_provider_user_records( + compact=test_license_record.compact, + provider_id=test_license_record.providerId, + ) + investigation_records = provider_user_records.get_investigation_records_for_license( + license_jurisdiction=test_license_record.jurisdiction, + license_type_abbreviation=test_license_record.licenseTypeAbbreviation, + ) + self.assertEqual(1, len(investigation_records)) + first_investigation_id = investigation_records[0].investigationId + + # Create second investigation + second_investigation_event = self.test_data_generator.generate_test_api_event( + sub_override=DEFAULT_AA_SUBMITTING_USER_ID, + scope_override=f'openid email {test_license_record.jurisdiction}/aslp.admin', + value_overrides={ + 'httpMethod': 'POST', + 'resource': LICENSE_INVESTIGATION_ENDPOINT_RESOURCE, + 'pathParameters': { + 'compact': test_license_record.compact, + 'providerId': str(test_license_record.providerId), + 'jurisdiction': test_license_record.jurisdiction, + 'licenseType': test_license_record.licenseTypeAbbreviation, + }, + }, + ) + + response = investigation_handler(second_investigation_event, self.mock_context) + self.assertEqual(200, response['statusCode']) + + # Get the second investigation ID + provider_user_records = self.config.data_client.get_provider_user_records( + compact=test_license_record.compact, + provider_id=test_license_record.providerId, + ) + investigation_records = provider_user_records.get_investigation_records_for_license( + license_jurisdiction=test_license_record.jurisdiction, + license_type_abbreviation=test_license_record.licenseTypeAbbreviation, + ) + self.assertEqual(2, len(investigation_records)) + second_investigation_id = [ + inv.investigationId for inv in investigation_records if inv.investigationId != first_investigation_id + ][0] + + # Close the second investigation + close_second_event = self.test_data_generator.generate_test_api_event( + sub_override=DEFAULT_AA_SUBMITTING_USER_ID, + scope_override=f'openid email {test_license_record.jurisdiction}/aslp.admin', + value_overrides={ + 'httpMethod': 'PATCH', + 'resource': LICENSE_INVESTIGATION_ID_ENDPOINT_RESOURCE, + 'pathParameters': { + 'compact': test_license_record.compact, + 'providerId': str(test_license_record.providerId), + 'jurisdiction': test_license_record.jurisdiction, + 'licenseType': test_license_record.licenseTypeAbbreviation, + 'investigationId': str(second_investigation_id), + }, + 'body': json.dumps({}), + }, + ) + + response = investigation_handler(close_second_event, self.mock_context) + self.assertEqual(200, response['statusCode']) + + # Verify that the license record still shows under investigation + provider_user_records = self.config.data_client.get_provider_user_records( + compact=test_license_record.compact, + provider_id=test_license_record.providerId, + ) + updated_license_record = provider_user_records.get_license_records()[0] + + self.assertEqual( + InvestigationStatusEnum.UNDER_INVESTIGATION, + updated_license_record.investigationStatus, + ) + + # Verify that one investigation is still visible in the API response + api_event = self.test_data_generator.generate_test_api_event( + scope_override=f'openid email {test_license_record.jurisdiction}/aslp.readGeneral', + value_overrides={ + 'httpMethod': 'GET', + 'resource': '/v1/compacts/{compact}/providers/{providerId}', + 'pathParameters': { + 'compact': test_license_record.compact, + 'providerId': str(test_license_record.providerId), + }, + }, + ) + + api_response = get_provider(api_event, self.mock_context) + self.assertEqual(200, api_response['statusCode']) + + provider_data = json.loads(api_response['body']) + license_obj = provider_data['licenses'][0] + + self.assertEqual(1, len(license_obj['investigations'])) + self.assertEqual(str(first_investigation_id), license_obj['investigations'][0]['investigationId']) + + # Verify that there are two INVESTIGATION update records + provider_user_records = self.config.data_client.get_provider_user_records( + compact=test_license_record.compact, + provider_id=test_license_record.providerId, + ) + update_records = provider_user_records.get_update_records_for_license( + jurisdiction=test_license_record.jurisdiction, license_type=test_license_record.licenseType + ) + + investigation_update_records = [ + record for record in update_records if record.updateType == UpdateCategory.INVESTIGATION + ] + self.assertEqual(2, len(investigation_update_records)) + + # Verify that there are no CLOSING_INVESTIGATION update records + closing_update_records = [ + record for record in update_records if record.updateType == UpdateCategory.CLOSING_INVESTIGATION + ] + self.assertEqual(0, len(closing_update_records)) + + # Verify that investigation closed event WAS published (should be 3 calls: 2 creation + 1 closure) + self.assertEqual(3, mock_publish_event.call_count) + call_types = [call[1]['detail_type'] for call in mock_publish_event.call_args_list] + self.assertEqual(2, call_types.count('license.investigation')) + self.assertEqual(1, call_types.count('license.investigationClosed')) + + # Now close the first investigation + close_first_event = self.test_data_generator.generate_test_api_event( + sub_override=DEFAULT_AA_SUBMITTING_USER_ID, + scope_override=f'openid email {test_license_record.jurisdiction}/aslp.admin', + value_overrides={ + 'httpMethod': 'PATCH', + 'resource': LICENSE_INVESTIGATION_ID_ENDPOINT_RESOURCE, + 'pathParameters': { + 'compact': test_license_record.compact, + 'providerId': str(test_license_record.providerId), + 'jurisdiction': test_license_record.jurisdiction, + 'licenseType': test_license_record.licenseTypeAbbreviation, + 'investigationId': str(first_investigation_id), + }, + 'body': json.dumps({}), + }, + ) + + response = investigation_handler(close_first_event, self.mock_context) + self.assertEqual(200, response['statusCode']) + + # Verify that the license record no longer has investigation status + provider_user_records = self.config.data_client.get_provider_user_records( + compact=test_license_record.compact, + provider_id=test_license_record.providerId, + ) + updated_license_record = provider_user_records.get_license_records()[0] + + self.assertIsNone(updated_license_record.investigationStatus) + + # Verify that there are no investigations visible in the API response + api_response = get_provider(api_event, self.mock_context) + self.assertEqual(200, api_response['statusCode']) + + provider_data = json.loads(api_response['body']) + license_obj = provider_data['licenses'][0] + + self.assertEqual(0, len(license_obj['investigations'])) + + # Verify that there are still two INVESTIGATION update records + provider_user_records = self.config.data_client.get_provider_user_records( + compact=test_license_record.compact, + provider_id=test_license_record.providerId, + ) + update_records = provider_user_records.get_update_records_for_license( + jurisdiction=test_license_record.jurisdiction, license_type=test_license_record.licenseType + ) + + investigation_update_records = [ + record for record in update_records if record.updateType == UpdateCategory.INVESTIGATION + ] + self.assertEqual(2, len(investigation_update_records)) + + # Verify that there is one CLOSING_INVESTIGATION update record + closing_update_records = [ + record for record in update_records if record.updateType == UpdateCategory.CLOSING_INVESTIGATION + ] + self.assertEqual(1, len(closing_update_records)) + + # Verify that investigation closed events were published (should be 4 calls total: 2 creation + 2 closure) + self.assertEqual(4, mock_publish_event.call_count) + call_types = [call[1]['detail_type'] for call in mock_publish_event.call_args_list] + self.assertEqual(2, call_types.count('license.investigation')) + self.assertEqual(2, call_types.count('license.investigationClosed')) 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 ff4e42968..11418ce5b 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 @@ -450,6 +450,7 @@ def test_public_get_provider_response_with_expected_fields_filtered(self): expected_provider.pop('licenses') expected_provider['privileges'][0].pop('attestations') expected_provider['privileges'][0].pop('compactTransactionId') + expected_provider['privileges'][0].pop('investigations') expected_provider.pop('dateOfExpiration') expected_provider.pop('jurisdictionUploadedLicenseStatus') expected_provider.pop('jurisdictionUploadedCompactEligibility') diff --git a/backend/compact-connect/lambdas/python/purchases/requirements-dev.in b/backend/compact-connect/lambdas/python/purchases/requirements-dev.in index 5a61b7b0d..3093094b6 100644 --- a/backend/compact-connect/lambdas/python/purchases/requirements-dev.in +++ b/backend/compact-connect/lambdas/python/purchases/requirements-dev.in @@ -1 +1,8 @@ moto[dynamodb, s3]>=5.0.12, <6 +pytest>=6.2.5 +pytest-cov +coverage +ruff +pip-tools +pip-audit +Faker>=37, <38 diff --git a/backend/compact-connect/lambdas/python/purchases/requirements-dev.txt b/backend/compact-connect/lambdas/python/purchases/requirements-dev.txt index 2a1ee6b0a..0468753c3 100644 --- a/backend/compact-connect/lambdas/python/purchases/requirements-dev.txt +++ b/backend/compact-connect/lambdas/python/purchases/requirements-dev.txt @@ -1,63 +1,146 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/purchases/requirements-dev.in +# pip-compile --no-emit-index-url --no-strip-extras lambdas/python/purchases/requirements-dev.in # -boto3==1.40.35 +boolean-py==5.0 + # via license-expression +boto3==1.40.56 # via moto -botocore==1.40.35 +botocore==1.40.56 # via # boto3 # moto # s3transfer -certifi==2025.8.3 +build==1.3.0 + # via pip-tools +cachecontrol[filecache]==0.14.3 + # via + # cachecontrol + # pip-audit +certifi==2025.10.5 # via requests cffi==2.0.0 # via cryptography -charset-normalizer==3.4.3 +charset-normalizer==3.4.4 # via requests -cryptography==46.0.1 +click==8.3.0 + # via pip-tools +coverage[toml]==7.11.0 + # via + # -r lambdas/python/purchases/requirements-dev.in + # pytest-cov +cryptography==46.0.3 # via moto +cyclonedx-python-lib==9.1.0 + # via pip-audit +defusedxml==0.7.1 + # via py-serializable docker==7.1.0 # via moto -idna==3.10 +faker==28.4.1 + # via -r lambdas/python/purchases/requirements-dev.in +filelock==3.20.0 + # via cachecontrol +idna==3.11 # via requests +iniconfig==2.3.0 + # via pytest jinja2==3.1.6 # via moto jmespath==1.0.1 # via # boto3 # botocore -markupsafe==3.0.2 +license-expression==30.4.4 + # via cyclonedx-python-lib +markdown-it-py==4.0.0 + # via rich +markupsafe==3.0.3 # via # jinja2 # werkzeug -moto[dynamodb,s3]==5.1.12 +mdurl==0.1.2 + # via markdown-it-py +moto[dynamodb,s3]==5.1.15 + # via -r lambdas/python/purchases/requirements-dev.in +msgpack==1.1.2 + # via cachecontrol +packageurl-python==0.17.5 + # via cyclonedx-python-lib +packaging==25.0 + # via + # build + # pip-audit + # pip-requirements-parser + # pytest +pip-api==0.0.34 + # via pip-audit +pip-audit==2.9.0 + # via -r lambdas/python/purchases/requirements-dev.in +pip-requirements-parser==32.0.1 + # via pip-audit +pip-tools==7.5.1 # via -r lambdas/python/purchases/requirements-dev.in +platformdirs==4.5.0 + # via pip-audit +pluggy==1.6.0 + # via + # pytest + # pytest-cov py-partiql-parser==0.6.1 # via moto +py-serializable==2.1.0 + # via cyclonedx-python-lib pycparser==2.23 # via cffi +pygments==2.19.2 + # via + # pytest + # rich +pyparsing==3.2.5 + # via pip-requirements-parser +pyproject-hooks==1.2.0 + # via + # build + # pip-tools +pytest==8.4.2 + # via + # -r lambdas/python/purchases/requirements-dev.in + # pytest-cov +pytest-cov==7.0.0 + # via -r lambdas/python/purchases/requirements-dev.in python-dateutil==2.9.0.post0 # via # botocore + # faker # moto -pyyaml==6.0.2 +pyyaml==6.0.3 # via # moto # responses requests==2.32.5 # via + # cachecontrol # docker # moto + # pip-audit # responses responses==0.25.8 # via moto +rich==14.2.0 + # via pip-audit +ruff==0.14.1 + # via -r lambdas/python/purchases/requirements-dev.in s3transfer==0.14.0 # via boto3 six==1.17.0 # via python-dateutil +sortedcontainers==2.4.0 + # via cyclonedx-python-lib +toml==0.10.2 + # via pip-audit urllib3==2.5.0 # via # botocore @@ -66,5 +149,11 @@ urllib3==2.5.0 # responses werkzeug==3.1.3 # via moto +wheel==0.45.1 + # via pip-tools xmltodict==1.0.2 # via moto + +# The following packages are considered to be unsafe in a requirements file: +# pip +# setuptools diff --git a/backend/compact-connect/lambdas/python/purchases/requirements.txt b/backend/compact-connect/lambdas/python/purchases/requirements.txt index 3b8e3df89..2718af8b8 100644 --- a/backend/compact-connect/lambdas/python/purchases/requirements.txt +++ b/backend/compact-connect/lambdas/python/purchases/requirements.txt @@ -1,16 +1,16 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/purchases/requirements.in +# pip-compile --no-emit-index-url --no-strip-extras lambdas/python/purchases/requirements.in # authorizenet==1.1.6 # via -r lambdas/python/purchases/requirements.in -certifi==2025.8.3 +certifi==2025.10.5 # via requests -charset-normalizer==3.4.3 +charset-normalizer==3.4.4 # via requests -idna==3.10 +idna==3.11 # via requests lxml==4.9.4 # via authorizenet diff --git a/backend/compact-connect/lambdas/python/staff-user-pre-token/requirements-dev.txt b/backend/compact-connect/lambdas/python/staff-user-pre-token/requirements-dev.txt index 5ea9fc177..afffac437 100644 --- a/backend/compact-connect/lambdas/python/staff-user-pre-token/requirements-dev.txt +++ b/backend/compact-connect/lambdas/python/staff-user-pre-token/requirements-dev.txt @@ -1,27 +1,27 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/staff-user-pre-token/requirements-dev.in +# pip-compile --no-emit-index-url --no-strip-extras lambdas/python/staff-user-pre-token/requirements-dev.in # -boto3==1.40.35 +boto3==1.40.56 # via moto -botocore==1.40.35 +botocore==1.40.56 # via # boto3 # moto # s3transfer -certifi==2025.8.3 +certifi==2025.10.5 # via requests cffi==2.0.0 # via cryptography -charset-normalizer==3.4.3 +charset-normalizer==3.4.4 # via requests -cryptography==46.0.1 +cryptography==46.0.3 # via moto docker==7.1.0 # via moto -idna==3.10 +idna==3.11 # via requests jinja2==3.1.6 # via moto @@ -29,11 +29,11 @@ jmespath==1.0.1 # via # boto3 # botocore -markupsafe==3.0.2 +markupsafe==3.0.3 # via # jinja2 # werkzeug -moto[dynamodb,s3]==5.1.12 +moto[dynamodb,s3]==5.1.15 # via -r lambdas/python/staff-user-pre-token/requirements-dev.in py-partiql-parser==0.6.1 # via moto @@ -43,7 +43,7 @@ python-dateutil==2.9.0.post0 # via # botocore # moto -pyyaml==6.0.2 +pyyaml==6.0.3 # via # moto # responses diff --git a/backend/compact-connect/lambdas/python/staff-user-pre-token/requirements.txt b/backend/compact-connect/lambdas/python/staff-user-pre-token/requirements.txt index 23d37d92a..43cf8504b 100644 --- a/backend/compact-connect/lambdas/python/staff-user-pre-token/requirements.txt +++ b/backend/compact-connect/lambdas/python/staff-user-pre-token/requirements.txt @@ -1,6 +1,6 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/staff-user-pre-token/requirements.in +# pip-compile --no-emit-index-url --no-strip-extras lambdas/python/staff-user-pre-token/requirements.in # diff --git a/backend/compact-connect/lambdas/python/staff-users/requirements-dev.in b/backend/compact-connect/lambdas/python/staff-users/requirements-dev.in index 0814db7f2..5d994613d 100644 --- a/backend/compact-connect/lambdas/python/staff-users/requirements-dev.in +++ b/backend/compact-connect/lambdas/python/staff-users/requirements-dev.in @@ -1,2 +1,2 @@ moto[dynamodb, s3, cognitoidp]>=5.0.15, <6 -Faker>=28,<29 +Faker>=37, <38 diff --git a/backend/compact-connect/lambdas/python/staff-users/requirements-dev.txt b/backend/compact-connect/lambdas/python/staff-users/requirements-dev.txt index 6e72a0e88..378def7bb 100644 --- a/backend/compact-connect/lambdas/python/staff-users/requirements-dev.txt +++ b/backend/compact-connect/lambdas/python/staff-users/requirements-dev.txt @@ -1,23 +1,23 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/staff-users/requirements-dev.in +# pip-compile --no-emit-index-url --no-strip-extras lambdas/python/staff-users/requirements-dev.in # -boto3==1.40.35 +boto3==1.40.56 # via moto -botocore==1.40.35 +botocore==1.40.56 # via # boto3 # moto # s3transfer -certifi==2025.8.3 +certifi==2025.10.5 # via requests cffi==2.0.0 # via cryptography -charset-normalizer==3.4.3 +charset-normalizer==3.4.4 # via requests -cryptography==46.0.1 +cryptography==46.0.3 # via # joserfc # moto @@ -25,7 +25,7 @@ docker==7.1.0 # via moto faker==28.4.1 # via -r lambdas/python/staff-users/requirements-dev.in -idna==3.10 +idna==3.11 # via requests jinja2==3.1.6 # via moto @@ -33,13 +33,13 @@ jmespath==1.0.1 # via # boto3 # botocore -joserfc==1.3.3 +joserfc==1.4.0 # via moto -markupsafe==3.0.2 +markupsafe==3.0.3 # via # jinja2 # werkzeug -moto[cognitoidp,dynamodb,s3]==5.1.12 +moto[cognitoidp,dynamodb,s3]==5.1.15 # via -r lambdas/python/staff-users/requirements-dev.in py-partiql-parser==0.6.1 # via moto @@ -50,7 +50,7 @@ python-dateutil==2.9.0.post0 # botocore # faker # moto -pyyaml==6.0.2 +pyyaml==6.0.3 # via # moto # responses diff --git a/backend/compact-connect/lambdas/python/staff-users/requirements.txt b/backend/compact-connect/lambdas/python/staff-users/requirements.txt index 3830b77fd..ac407fa42 100644 --- a/backend/compact-connect/lambdas/python/staff-users/requirements.txt +++ b/backend/compact-connect/lambdas/python/staff-users/requirements.txt @@ -1,6 +1,6 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/staff-users/requirements.in +# pip-compile --no-emit-index-url --no-strip-extras lambdas/python/staff-users/requirements.in # diff --git a/backend/compact-connect/requirements-dev.in b/backend/compact-connect/requirements-dev.in index b63c88c44..fc6f66734 100644 --- a/backend/compact-connect/requirements-dev.in +++ b/backend/compact-connect/requirements-dev.in @@ -4,4 +4,4 @@ coverage ruff pip-tools pip-audit -Faker>=28,<29 +Faker>=37, <38 diff --git a/backend/compact-connect/requirements-dev.txt b/backend/compact-connect/requirements-dev.txt index 82a9ca638..6b3eed0b4 100644 --- a/backend/compact-connect/requirements-dev.txt +++ b/backend/compact-connect/requirements-dev.txt @@ -1,8 +1,8 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None requirements-dev.in +# pip-compile --no-emit-index-url --no-strip-extras requirements-dev.in # boolean-py==5.0 # via license-expression @@ -12,13 +12,13 @@ cachecontrol[filecache]==0.14.3 # via # cachecontrol # pip-audit -certifi==2025.8.3 +certifi==2025.10.5 # via requests -charset-normalizer==3.4.3 +charset-normalizer==3.4.4 # via requests click==8.3.0 # via pip-tools -coverage[toml]==7.10.6 +coverage[toml]==7.11.0 # via # -r requirements-dev.in # pytest-cov @@ -28,11 +28,11 @@ defusedxml==0.7.1 # via py-serializable faker==28.4.1 # via -r requirements-dev.in -filelock==3.19.1 +filelock==3.20.0 # via cachecontrol -idna==3.10 +idna==3.11 # via requests -iniconfig==2.1.0 +iniconfig==2.3.0 # via pytest license-expression==30.4.4 # via cyclonedx-python-lib @@ -40,7 +40,7 @@ markdown-it-py==4.0.0 # via rich mdurl==0.1.2 # via markdown-it-py -msgpack==1.1.1 +msgpack==1.1.2 # via cachecontrol packageurl-python==0.17.5 # via cyclonedx-python-lib @@ -56,9 +56,9 @@ pip-audit==2.9.0 # via -r requirements-dev.in pip-requirements-parser==32.0.1 # via pip-audit -pip-tools==7.5.0 +pip-tools==7.5.1 # via -r requirements-dev.in -platformdirs==4.4.0 +platformdirs==4.5.0 # via pip-audit pluggy==1.6.0 # via @@ -70,7 +70,7 @@ pygments==2.19.2 # via # pytest # rich -pyparsing==3.2.4 +pyparsing==3.2.5 # via pip-requirements-parser pyproject-hooks==1.2.0 # via @@ -88,9 +88,9 @@ requests==2.32.5 # via # cachecontrol # pip-audit -rich==14.1.0 +rich==14.2.0 # via pip-audit -ruff==0.13.1 +ruff==0.14.1 # via -r requirements-dev.in six==1.17.0 # via python-dateutil diff --git a/backend/compact-connect/requirements.txt b/backend/compact-connect/requirements.txt index 97e6c37ff..512097dfd 100644 --- a/backend/compact-connect/requirements.txt +++ b/backend/compact-connect/requirements.txt @@ -1,10 +1,10 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None requirements.in +# pip-compile --no-emit-index-url --no-strip-extras requirements.in # -attrs==25.3.0 +attrs==25.4.0 # via # cattrs # jsii @@ -12,18 +12,18 @@ aws-cdk-asset-awscli-v1==2.2.242 # via aws-cdk-lib aws-cdk-asset-node-proxy-agent-v6==2.1.0 # via aws-cdk-lib -aws-cdk-aws-lambda-python-alpha==2.215.0a0 +aws-cdk-aws-lambda-python-alpha==2.220.0a0 # via -r requirements.in -aws-cdk-cloud-assembly-schema==48.10.0 +aws-cdk-cloud-assembly-schema==48.16.0 # via aws-cdk-lib -aws-cdk-lib==2.215.0 +aws-cdk-lib==2.220.0 # via # -r requirements.in # aws-cdk-aws-lambda-python-alpha # cdk-nag -cattrs==25.2.0 +cattrs==25.3.0 # via jsii -cdk-nag==2.37.31 +cdk-nag==2.37.55 # via -r requirements.in constructs==10.4.2 # via @@ -33,7 +33,7 @@ constructs==10.4.2 # cdk-nag importlib-resources==6.5.2 # via jsii -jsii==1.114.1 +jsii==1.117.0 # via # aws-cdk-asset-awscli-v1 # aws-cdk-asset-node-proxy-agent-v6 @@ -54,7 +54,7 @@ publication==0.0.3 # jsii python-dateutil==2.9.0.post0 # via jsii -pyyaml==6.0.2 +pyyaml==6.0.3 # via -r requirements.in six==1.17.0 # via python-dateutil 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 54b61ac22..8253981ed 100644 --- a/backend/compact-connect/stacks/api_lambda_stack/provider_management.py +++ b/backend/compact-connect/stacks/api_lambda_stack/provider_management.py @@ -31,11 +31,11 @@ def __init__( self.stack: Stack = Stack.of(scope) lambda_environment = { 'PROVIDER_TABLE_NAME': persistent_stack.provider_table.table_name, + 'EVENT_BUS_NAME': data_event_bus.event_bus_name, 'PROV_FAM_GIV_MID_INDEX_NAME': persistent_stack.provider_table.provider_fam_giv_mid_index_name, 'PROV_DATE_OF_UPDATE_INDEX_NAME': persistent_stack.provider_table.provider_date_of_update_index_name, 'SSN_TABLE_NAME': persistent_stack.ssn_table.table_name, 'SSN_INDEX_NAME': persistent_stack.ssn_table.ssn_index_name, - 'EVENT_BUS_NAME': data_event_bus.event_bus_name, 'RATE_LIMITING_TABLE_NAME': persistent_stack.rate_limiting_table.table_name, 'USER_POOL_ID': persistent_stack.staff_users.user_pool_id, 'EMAIL_NOTIFICATION_SERVICE_LAMBDA_NAME': persistent_stack.email_notification_service_lambda.function_name, @@ -45,6 +45,8 @@ def __init__( } # Create all the lambda handlers + self.provider_investigation_handler = self._create_provider_investigation_handler(lambda_environment) + api_lambda_stack.log_groups.append(self.provider_investigation_handler.log_group) self.get_provider_handler = self._get_provider_handler(lambda_environment) api_lambda_stack.log_groups.append(self.get_provider_handler.log_group) self.query_providers_handler = self._query_providers_handler(lambda_environment) @@ -56,6 +58,38 @@ def __init__( self.provider_encumbrance_handler = self._add_provider_encumbrance_handler(lambda_environment) api_lambda_stack.log_groups.append(self.provider_encumbrance_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.""" + handler = PythonFunction( + self.scope, + 'ProviderInvestigationHandler', + description='Provider investigation handler', + lambda_dir='provider-data-v1', + index=os.path.join('handlers', 'investigation.py'), + handler='investigation_handler', + environment=lambda_environment, + alarm_topic=self.persistent_stack.alarm_topic, + ) + + # Grant necessary permissions + 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( + self.stack, + path=f'{handler.role.node.path}/DefaultPolicy/Resource', + suppressions=[ + { + 'id': 'AwsSolutions-IAM5', + 'reason': 'The actions in this policy are specifically what this lambda needs to read ' + 'and is scoped to tables and an event bus.', + }, + ], + ) + + return handler + def _get_provider_handler( self, lambda_environment: dict, @@ -333,4 +367,5 @@ def _add_provider_encumbrance_handler( }, ], ) + return handler diff --git a/backend/compact-connect/stacks/api_stack/v1_api/api.py b/backend/compact-connect/stacks/api_stack/v1_api/api.py index bd2f933c0..f356ff672 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/api.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/api.py @@ -166,8 +166,8 @@ def __init__( admin_method_options=admin_auth_method_options, ssn_method_options=read_ssn_auth_method_options, api_model=self.api_model, - privilege_history_function=privilege_history_handler, api_lambda_stack=api_lambda_stack, + privilege_history_function=privilege_history_handler, ) # GET /v1/compacts/{compact}/jurisdictions self.jurisdictions_resource = self.compact_resource.add_resource('jurisdictions') diff --git a/backend/compact-connect/stacks/api_stack/v1_api/api_model.py b/backend/compact-connect/stacks/api_stack/v1_api/api_model.py index d29b30def..a774757a9 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 @@ -411,31 +411,7 @@ def post_privilege_encumbrance_request_model(self) -> Model: self.api._v1_post_privilege_encumbrance_request_model = self.api.add_model( 'V1PostPrivilegeEncumbranceRequestModel', description='Post privilege encumbrance request model', - schema=JsonSchema( - type=JsonSchemaType.OBJECT, - additional_properties=False, - required=['encumbranceEffectiveDate', 'encumbranceType'], - properties={ - 'encumbranceEffectiveDate': JsonSchema( - type=JsonSchemaType.STRING, - description='The effective date of the encumbrance', - format='date', - pattern=cc_api.YMD_FORMAT, - ), - 'encumbranceType': self._encumbrance_type_schema, - # TODO - remove this after migrating to 'clinicalPrivilegeActionCategories' field # noqa: FIX002 - 'clinicalPrivilegeActionCategory': JsonSchema( - type=JsonSchemaType.STRING, - description='(Deprecated) The category of clinical privilege action. ' - 'Use clinicalPrivilegeActionCategories instead.', - ), - 'clinicalPrivilegeActionCategories': JsonSchema( - type=JsonSchemaType.ARRAY, - description='The categories of clinical privilege action', - items=JsonSchema(type=JsonSchemaType.STRING), - ), - }, - ), + schema=self._encumbrance_request_schema, ) return self.api._v1_post_privilege_encumbrance_request_model @@ -448,31 +424,7 @@ def post_license_encumbrance_request_model(self) -> Model: self.api._v1_post_license_encumbrance_request_model = self.api.add_model( 'V1PostLicenseEncumbranceRequestModel', description='Post license encumbrance request model', - schema=JsonSchema( - type=JsonSchemaType.OBJECT, - additional_properties=False, - required=['encumbranceEffectiveDate', 'encumbranceType'], - properties={ - 'encumbranceEffectiveDate': JsonSchema( - type=JsonSchemaType.STRING, - description='The effective date of the encumbrance', - format='date', - pattern=cc_api.YMD_FORMAT, - ), - 'encumbranceType': self._encumbrance_type_schema, - # TODO - remove this after migrating to 'clinicalPrivilegeActionCategories' field # noqa: FIX002 - 'clinicalPrivilegeActionCategory': JsonSchema( - type=JsonSchemaType.STRING, - description='(Deprecated) The category of clinical privilege action. ' - 'Use clinicalPrivilegeActionCategories instead.', - ), - 'clinicalPrivilegeActionCategories': JsonSchema( - type=JsonSchemaType.ARRAY, - description='The categories of clinical privilege action', - items=JsonSchema(type=JsonSchemaType.STRING), - ), - }, - ), + schema=self._encumbrance_request_schema, ) return self.api._v1_post_license_encumbrance_request_model @@ -607,8 +559,7 @@ def post_provider_military_affiliation_response_model(self) -> Model: 'dateOfUpdate': JsonSchema( type=JsonSchemaType.STRING, description='The date the document was last updated', - format='date', - pattern=cc_api.YMD_FORMAT, + format='date-time', ), 'documentUploadFields': JsonSchema( type=JsonSchemaType.ARRAY, @@ -1077,8 +1028,7 @@ def _provider_detail_response_schema(self): 'licenseType': JsonSchema(type=JsonSchemaType.STRING, enum=self.stack.license_type_names), 'dateOfUpdate': JsonSchema( type=JsonSchemaType.STRING, - format='date', - pattern=cc_api.YMD_FORMAT, + format='date-time', ), 'licenseStatus': JsonSchema(type=JsonSchemaType.STRING, enum=['active', 'inactive']), 'compactEligibility': JsonSchema( @@ -1116,9 +1066,7 @@ def _provider_detail_response_schema(self): 'licenseType': JsonSchema( type=JsonSchemaType.STRING, enum=self.stack.license_type_names ), - 'dateOfUpdate': JsonSchema( - type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT - ), + 'dateOfUpdate': JsonSchema(type=JsonSchemaType.STRING, format='date-time'), 'previous': JsonSchema( type=JsonSchemaType.OBJECT, required=[ @@ -1210,9 +1158,7 @@ def _provider_detail_response_schema(self): 'effectiveLiftDate': JsonSchema( type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT ), - 'dateOfUpdate': JsonSchema( - type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT - ), + 'dateOfUpdate': JsonSchema(type=JsonSchemaType.STRING, format='date-time'), 'encumbranceType': JsonSchema(type=JsonSchemaType.STRING), # TODO - remove this after migrating to list field # noqa: FIX002 'clinicalPrivilegeActionCategory': JsonSchema(type=JsonSchemaType.STRING), @@ -1225,6 +1171,15 @@ def _provider_detail_response_schema(self): }, ), ), + 'investigations': JsonSchema( + type=JsonSchemaType.ARRAY, + items=self._investigation_schema, + ), + 'investigationStatus': JsonSchema( + type=JsonSchemaType.STRING, + enum=['underInvestigation'], + description='Status indicating if the license is under investigation', + ), **self._common_license_properties, }, ), @@ -1277,9 +1232,7 @@ def _provider_detail_response_schema(self): 'licenseType': JsonSchema( type=JsonSchemaType.STRING, enum=self.stack.license_type_names ), - 'dateOfUpdate': JsonSchema( - type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT - ), + 'dateOfUpdate': JsonSchema(type=JsonSchemaType.STRING, format='date-time'), 'previous': JsonSchema( type=JsonSchemaType.OBJECT, required=[ @@ -1351,9 +1304,7 @@ def _provider_detail_response_schema(self): 'effectiveLiftDate': JsonSchema( type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT ), - 'dateOfUpdate': JsonSchema( - type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT - ), + 'dateOfUpdate': JsonSchema(type=JsonSchemaType.STRING, format='date-time'), 'encumbranceType': JsonSchema(type=JsonSchemaType.STRING), # TODO - remove this after migrating to list field # noqa: FIX002 'clinicalPrivilegeActionCategory': JsonSchema(type=JsonSchemaType.STRING), @@ -1366,6 +1317,15 @@ def _provider_detail_response_schema(self): }, ), ), + 'investigations': JsonSchema( + type=JsonSchemaType.ARRAY, + items=self._investigation_schema, + ), + 'investigationStatus': JsonSchema( + type=JsonSchemaType.STRING, + enum=['underInvestigation'], + description='Status indicating if the privilege is under investigation', + ), **self._common_privilege_properties, }, ), @@ -1386,9 +1346,7 @@ def _provider_detail_response_schema(self): ], properties={ 'type': JsonSchema(type=JsonSchemaType.STRING, enum=['militaryAffiliation']), - 'dateOfUpdate': JsonSchema( - type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT - ), + 'dateOfUpdate': JsonSchema(type=JsonSchemaType.STRING, format='date-time'), 'providerId': JsonSchema(type=JsonSchemaType.STRING, pattern=cc_api.UUID4_FORMAT), 'compact': JsonSchema( type=JsonSchemaType.STRING, enum=self.stack.node.get_context('compacts') @@ -1443,6 +1401,37 @@ def _update_type_schema(self) -> JsonSchema: ], ) + @property + def _encumbrance_request_schema(self) -> JsonSchema: + """Common schema for encumbrance request data used in both POST and PATCH investigation endpoints""" + return JsonSchema( + type=JsonSchemaType.OBJECT, + description='Encumbrance data to create', + additional_properties=False, + # TODO - add clinicalPrivilegeActionCategories after migrating # noqa: FIX002 + required=['encumbranceEffectiveDate', 'encumbranceType'], + properties={ + 'encumbranceEffectiveDate': JsonSchema( + type=JsonSchemaType.STRING, + description='The effective date of the encumbrance', + format='date', + pattern=cc_api.YMD_FORMAT, + ), + 'encumbranceType': self._encumbrance_type_schema, + # TODO - remove this after migrating to 'clinicalPrivilegeActionCategories' field # noqa: FIX002 + 'clinicalPrivilegeActionCategory': JsonSchema( + type=JsonSchemaType.STRING, + description='(Deprecated) The category of clinical privilege action. ' + 'Use clinicalPrivilegeActionCategories instead.', + ), + 'clinicalPrivilegeActionCategories': JsonSchema( + type=JsonSchemaType.ARRAY, + description='The categories of clinical privilege action', + items=JsonSchema(type=JsonSchemaType.STRING), + ), + }, + ) + @property def _encumbrance_type_schema(self) -> JsonSchema: """Common schema for encumbrance type field""" @@ -1468,6 +1457,38 @@ def _encumbrance_type_schema(self) -> JsonSchema: ], ) + @property + def _investigation_schema(self) -> JsonSchema: + """Common schema for investigation objects""" + return JsonSchema( + type=JsonSchemaType.OBJECT, + required=[ + 'type', + 'compact', + 'providerId', + 'investigationId', + 'jurisdiction', + 'licenseType', + 'dateOfUpdate', + 'creationDate', + 'submittingUser', + ], + properties={ + 'type': JsonSchema(type=JsonSchemaType.STRING, enum=['investigation']), + 'compact': JsonSchema(type=JsonSchemaType.STRING, enum=self.stack.node.get_context('compacts')), + 'providerId': JsonSchema(type=JsonSchemaType.STRING, pattern=cc_api.UUID4_FORMAT), + 'investigationId': JsonSchema(type=JsonSchemaType.STRING), + 'jurisdiction': JsonSchema( + type=JsonSchemaType.STRING, + enum=self.stack.node.get_context('jurisdictions'), + ), + 'licenseType': JsonSchema(type=JsonSchemaType.STRING), + 'dateOfUpdate': JsonSchema(type=JsonSchemaType.STRING, format='date-time'), + 'creationDate': JsonSchema(type=JsonSchemaType.STRING, format='date-time'), + 'submittingUser': JsonSchema(type=JsonSchemaType.STRING), + }, + ) + @property def _common_license_properties(self) -> dict: return { @@ -1542,7 +1563,7 @@ def _common_provider_properties(self) -> dict: max_length=100, ), 'currentHomeJurisdiction': self.current_home_jurisdiction_selection_field, - 'dateOfUpdate': JsonSchema(type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT), + 'dateOfUpdate': JsonSchema(type=JsonSchemaType.STRING, format='date-time'), } @property @@ -1555,7 +1576,7 @@ def _common_privilege_properties(self) -> dict: 'dateOfIssuance': JsonSchema(type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT), 'dateOfRenewal': JsonSchema(type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT), 'dateOfExpiration': JsonSchema(type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT), - 'dateOfUpdate': JsonSchema(type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT), + 'dateOfUpdate': JsonSchema(type=JsonSchemaType.STRING, format='date-time'), 'compactTransactionId': JsonSchema(type=JsonSchemaType.STRING), 'privilegeId': JsonSchema(type=JsonSchemaType.STRING), 'licenseJurisdiction': JsonSchema( @@ -2329,15 +2350,11 @@ def privilege_history_response_model(self) -> Model: properties={ 'type': JsonSchema(type=JsonSchemaType.STRING, enum=['privilegeUpdate']), 'updateType': self._update_type_schema, - 'dateOfUpdate': JsonSchema( - type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT - ), + 'dateOfUpdate': JsonSchema(type=JsonSchemaType.STRING, format='date-time'), 'effectiveDate': JsonSchema( type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT ), - 'createDate': JsonSchema( - type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT - ), + 'createDate': JsonSchema(type=JsonSchemaType.STRING, format='date-time'), 'note': JsonSchema(type=JsonSchemaType.STRING), }, ), @@ -2445,7 +2462,7 @@ def _public_privilege_response_schema(self): 'dateOfIssuance': JsonSchema(type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT), 'dateOfRenewal': JsonSchema(type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT), 'dateOfExpiration': JsonSchema(type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT), - 'dateOfUpdate': JsonSchema(type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT), + 'dateOfUpdate': JsonSchema(type=JsonSchemaType.STRING, format='date-time'), 'privilegeId': JsonSchema(type=JsonSchemaType.STRING), 'administratorSetStatus': JsonSchema(type=JsonSchemaType.STRING, enum=['active', 'inactive']), 'status': JsonSchema(type=JsonSchemaType.STRING, enum=['active', 'inactive']), @@ -2474,9 +2491,7 @@ def _public_privilege_response_schema(self): enum=stack.node.get_context('jurisdictions'), ), 'licenseType': JsonSchema(type=JsonSchemaType.STRING, enum=self.stack.license_type_names), - 'dateOfUpdate': JsonSchema( - type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT - ), + 'dateOfUpdate': JsonSchema(type=JsonSchemaType.STRING, format='date-time'), 'previous': JsonSchema( type=JsonSchemaType.OBJECT, required=[ @@ -2501,9 +2516,7 @@ def _public_privilege_response_schema(self): 'dateOfRenewal': JsonSchema( type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT ), - 'dateOfUpdate': JsonSchema( - type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT - ), + 'dateOfUpdate': JsonSchema(type=JsonSchemaType.STRING, format='date-time'), 'licenseJurisdiction': JsonSchema( type=JsonSchemaType.STRING, enum=stack.node.get_context('jurisdictions'), @@ -2526,9 +2539,7 @@ def _public_privilege_response_schema(self): 'dateOfRenewal': JsonSchema( type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT ), - 'dateOfUpdate': JsonSchema( - type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT - ), + 'dateOfUpdate': JsonSchema(type=JsonSchemaType.STRING, format='date-time'), 'licenseJurisdiction': JsonSchema( type=JsonSchemaType.STRING, enum=stack.node.get_context('jurisdictions'), @@ -2576,9 +2587,7 @@ def _public_privilege_response_schema(self): 'effectiveLiftDate': JsonSchema( type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT ), - 'dateOfUpdate': JsonSchema( - type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT - ), + 'dateOfUpdate': JsonSchema(type=JsonSchemaType.STRING, format='date-time'), }, ), ), @@ -2701,7 +2710,7 @@ def _common_public_provider_properties(self) -> dict: items=JsonSchema(type=JsonSchemaType.STRING, enum=stack.node.get_context('jurisdictions')), ), 'currentHomeJurisdiction': self.current_home_jurisdiction_selection_field, - 'dateOfUpdate': JsonSchema(type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT), + 'dateOfUpdate': JsonSchema(type=JsonSchemaType.STRING, format='date-time'), } @property @@ -2809,3 +2818,67 @@ def check_feature_flag_response_model(self) -> Model: ), ) return self.api._v1_check_feature_flag_response_model + + @property + def post_privilege_investigation_request_model(self) -> Model: + """POST privilege investigation request model""" + if not hasattr(self.api, '_v1_post_privilege_investigation_request_model'): + self.api._v1_post_privilege_investigation_request_model = self.api.add_model( + 'V1PostPrivilegeInvestigationRequestModel', + description='Post privilege investigation request model', + schema=JsonSchema( + type=JsonSchemaType.OBJECT, + properties={}, + ), + ) + return self.api._v1_post_privilege_investigation_request_model + + @property + def post_license_investigation_request_model(self) -> Model: + """POST license investigation request model""" + if not hasattr(self.api, '_v1_post_license_investigation_request_model'): + self.api._v1_post_license_investigation_request_model = self.api.add_model( + 'V1PostLicenseInvestigationRequestModel', + description='Post license investigation request model', + schema=JsonSchema( + type=JsonSchemaType.OBJECT, + properties={}, + ), + ) + return self.api._v1_post_license_investigation_request_model + + @property + def patch_privilege_investigation_request_model(self) -> Model: + """PATCH privilege investigation request model""" + if not hasattr(self.api, '_v1_patch_privilege_investigation_request_model'): + self.api._v1_patch_privilege_investigation_request_model = self.api.add_model( + 'V1PatchPrivilegeInvestigationRequestModel', + description='Patch privilege investigation request model', + schema=JsonSchema( + type=JsonSchemaType.OBJECT, + required=['action'], + properties={ + 'action': JsonSchema(type=JsonSchemaType.STRING, enum=['close']), + 'encumbrance': self._encumbrance_request_schema, + }, + ), + ) + return self.api._v1_patch_privilege_investigation_request_model + + @property + def patch_license_investigation_request_model(self) -> Model: + """PATCH license investigation request model""" + if not hasattr(self.api, '_v1_patch_license_investigation_request_model'): + self.api._v1_patch_license_investigation_request_model = self.api.add_model( + 'V1PatchLicenseInvestigationRequestModel', + description='Patch license investigation request model', + schema=JsonSchema( + type=JsonSchemaType.OBJECT, + required=['action'], + properties={ + 'action': JsonSchema(type=JsonSchemaType.STRING, enum=['close']), + 'encumbrance': self._encumbrance_request_schema, + }, + ), + ) + return self.api._v1_patch_license_investigation_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 db7eedd4e..df50ec15e 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 @@ -57,21 +57,42 @@ def __init__( 'licenseType' ).add_resource('{licenseType}') - # Reference lambda handlers from api_lambda_stack - self.get_provider_handler = api_lambda_stack.provider_management_lambdas.get_provider_handler - self.query_providers_handler = api_lambda_stack.provider_management_lambdas.query_providers_handler - self.get_provider_ssn_handler = api_lambda_stack.provider_management_lambdas.get_provider_ssn_handler - self.deactivate_privilege_handler = api_lambda_stack.provider_management_lambdas.deactivate_privilege_handler - self.provider_encumbrance_handler = api_lambda_stack.provider_management_lambdas.provider_encumbrance_handler + self._add_query_providers( + method_options=method_options, + query_providers_handler=api_lambda_stack.provider_management_lambdas.query_providers_handler, + ) + self._add_get_provider( + method_options=method_options, + get_provider_handler=api_lambda_stack.provider_management_lambdas.get_provider_handler, + ) + self._add_get_provider_ssn( + method_options=ssn_method_options, + get_provider_ssn_handler=api_lambda_stack.provider_management_lambdas.get_provider_ssn_handler, + ) + self._add_deactivate_privilege( + method_options=admin_method_options, + deactivate_privilege_handler=api_lambda_stack.provider_management_lambdas.deactivate_privilege_handler, + ) - self._add_query_providers(method_options=method_options) - self._add_get_provider(method_options=method_options) - self._add_get_provider_ssn(method_options=ssn_method_options) - self._add_deactivate_privilege(method_options=admin_method_options) + self._add_encumber_privilege( + method_options=admin_method_options, + provider_encumbrance_handler=api_lambda_stack.provider_management_lambdas.provider_encumbrance_handler, + ) - self._add_encumber_privilege(method_options=admin_method_options) + self._add_encumber_license( + method_options=admin_method_options, + provider_encumbrance_handler=api_lambda_stack.provider_management_lambdas.provider_encumbrance_handler, + ) + + self._add_investigation_privilege( + method_options=admin_method_options, + investigation_handler=api_lambda_stack.provider_management_lambdas.provider_investigation_handler, + ) - self._add_encumber_license(method_options=admin_method_options) + self._add_investigation_license( + method_options=admin_method_options, + investigation_handler=api_lambda_stack.provider_management_lambdas.provider_investigation_handler, + ) self._add_get_privilege_history( method_options=method_options, @@ -81,6 +102,7 @@ def __init__( def _add_get_provider( self, method_options: MethodOptions, + get_provider_handler: PythonFunction, ): self.provider_resource.add_method( 'GET', @@ -91,7 +113,7 @@ def _add_get_provider( response_models={'application/json': self.api_model.provider_response_model}, ), ], - integration=LambdaIntegration(self.get_provider_handler, timeout=Duration.seconds(29)), + integration=LambdaIntegration(get_provider_handler, timeout=Duration.seconds(29)), request_parameters={'method.request.header.Authorization': True}, authorization_type=method_options.authorization_type, authorizer=method_options.authorizer, @@ -101,6 +123,7 @@ def _add_get_provider( def _add_query_providers( self, method_options: MethodOptions, + query_providers_handler: PythonFunction, ): query_resource = self.resource.add_resource('query') @@ -114,7 +137,7 @@ def _add_query_providers( response_models={'application/json': self.api_model.query_providers_response_model}, ), ], - integration=LambdaIntegration(self.query_providers_handler, timeout=Duration.seconds(29)), + integration=LambdaIntegration(query_providers_handler, timeout=Duration.seconds(29)), request_parameters={'method.request.header.Authorization': True}, authorization_type=method_options.authorization_type, authorizer=method_options.authorizer, @@ -124,6 +147,7 @@ def _add_query_providers( def _add_get_provider_ssn( self, method_options: MethodOptions, + get_provider_ssn_handler: PythonFunction, ): """Add GET /providers/{providerId}/ssn endpoint to retrieve a provider's SSN.""" # Add the SSN endpoint as a sub-resource of the provider @@ -137,7 +161,7 @@ def _add_get_provider_ssn( response_models={'application/json': self.api_model.get_provider_ssn_response_model}, ), ], - integration=LambdaIntegration(self.get_provider_ssn_handler, timeout=Duration.seconds(29)), + integration=LambdaIntegration(get_provider_ssn_handler, timeout=Duration.seconds(29)), request_parameters={'method.request.header.Authorization': True}, authorization_type=method_options.authorization_type, authorizer=method_options.authorizer, @@ -173,6 +197,7 @@ def _add_get_provider_ssn( def _add_deactivate_privilege( self, method_options: MethodOptions, + deactivate_privilege_handler: PythonFunction, ): """Add POST /providers/{providerId}/privileges/jurisdiction/{jurisdiction} /licenseType/{licenseType}/deactivate endpoint.""" @@ -212,7 +237,7 @@ def _add_deactivate_privilege( response_models={'application/json': self.api_model.message_response_model}, ), ], - integration=LambdaIntegration(self.deactivate_privilege_handler, timeout=Duration.seconds(29)), + integration=LambdaIntegration(deactivate_privilege_handler, timeout=Duration.seconds(29)), request_parameters={'method.request.header.Authorization': True}, authorization_type=method_options.authorization_type, authorizer=method_options.authorizer, @@ -222,6 +247,7 @@ def _add_deactivate_privilege( def _add_encumber_privilege( self, method_options: MethodOptions, + provider_encumbrance_handler: PythonFunction, ): """Add POST /providers/{providerId}/privileges/jurisdiction/{jurisdiction} /licenseType/{licenseType}/encumbrance endpoint.""" @@ -238,7 +264,7 @@ def _add_encumber_privilege( response_models={'application/json': self.api_model.message_response_model}, ), ], - integration=LambdaIntegration(self.provider_encumbrance_handler, timeout=Duration.seconds(29)), + integration=LambdaIntegration(provider_encumbrance_handler, timeout=Duration.seconds(29)), request_parameters={'method.request.header.Authorization': True}, authorization_type=method_options.authorization_type, authorizer=method_options.authorizer, @@ -257,7 +283,7 @@ def _add_encumber_privilege( response_models={'application/json': self.api_model.message_response_model}, ), ], - integration=LambdaIntegration(self.provider_encumbrance_handler, timeout=Duration.seconds(29)), + integration=LambdaIntegration(provider_encumbrance_handler, timeout=Duration.seconds(29)), request_parameters={'method.request.header.Authorization': True}, authorization_type=method_options.authorization_type, authorizer=method_options.authorizer, @@ -267,6 +293,7 @@ def _add_encumber_privilege( def _add_encumber_license( self, method_options: MethodOptions, + provider_encumbrance_handler: PythonFunction, ): """Add POST /providers/{providerId}/licenses/jurisdiction/{jurisdiction} /licenseType/{licenseType}/encumbrance endpoint.""" @@ -281,7 +308,7 @@ def _add_encumber_license( response_models={'application/json': self.api_model.message_response_model}, ), ], - integration=LambdaIntegration(self.provider_encumbrance_handler, timeout=Duration.seconds(29)), + integration=LambdaIntegration(provider_encumbrance_handler, timeout=Duration.seconds(29)), request_parameters={'method.request.header.Authorization': True}, authorization_type=method_options.authorization_type, authorizer=method_options.authorizer, @@ -300,7 +327,101 @@ def _add_encumber_license( response_models={'application/json': self.api_model.message_response_model}, ), ], - integration=LambdaIntegration(self.provider_encumbrance_handler, timeout=Duration.seconds(29)), + integration=LambdaIntegration(provider_encumbrance_handler, timeout=Duration.seconds(29)), + request_parameters={'method.request.header.Authorization': True}, + authorization_type=method_options.authorization_type, + authorizer=method_options.authorizer, + authorization_scopes=method_options.authorization_scopes, + ) + + def _add_investigation_privilege( + self, + method_options: MethodOptions, + investigation_handler: PythonFunction, + ): + """Add POST /providers/{providerId}/privileges/jurisdiction/{jurisdiction} + /licenseType/{licenseType}/investigation endpoint.""" + self.investigation_privilege_resource = self.privilege_jurisdiction_license_type_resource.add_resource( + 'investigation' + ) + self.investigation_privilege_resource.add_method( + 'POST', + request_validator=self.api.parameter_body_validator, + request_models={'application/json': self.api_model.post_privilege_investigation_request_model}, + method_responses=[ + MethodResponse( + status_code='200', + response_models={'application/json': self.api_model.message_response_model}, + ), + ], + integration=LambdaIntegration(investigation_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, + ) + + # Add PATCH method for closing privilege investigations - now with investigationId in path + self.investigation_privilege_id_resource = self.investigation_privilege_resource.add_resource( + '{investigationId}' + ) + self.investigation_privilege_id_resource.add_method( + 'PATCH', + request_validator=self.api.parameter_body_validator, + request_models={'application/json': self.api_model.patch_privilege_investigation_request_model}, + method_responses=[ + MethodResponse( + status_code='200', + response_models={'application/json': self.api_model.message_response_model}, + ), + ], + integration=LambdaIntegration(investigation_handler, timeout=Duration.seconds(29)), + request_parameters={'method.request.header.Authorization': True}, + authorization_type=method_options.authorization_type, + authorizer=method_options.authorizer, + authorization_scopes=method_options.authorization_scopes, + ) + + def _add_investigation_license( + self, + method_options: MethodOptions, + investigation_handler: PythonFunction, + ): + """Add POST /providers/{providerId}/licenses/jurisdiction/{jurisdiction} + /licenseType/{licenseType}/investigation endpoint.""" + self.investigation_license_resource = self.license_jurisdiction_license_type_resource.add_resource( + 'investigation' + ) + self.investigation_license_resource.add_method( + 'POST', + request_validator=self.api.parameter_body_validator, + request_models={'application/json': self.api_model.post_license_investigation_request_model}, + method_responses=[ + MethodResponse( + status_code='200', + response_models={'application/json': self.api_model.message_response_model}, + ), + ], + integration=LambdaIntegration(investigation_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, + ) + + # Add PATCH method for closing license investigations - now with investigationId in path + self.investigation_license_id_resource = self.investigation_license_resource.add_resource('{investigationId}') + self.investigation_license_id_resource.add_method( + 'PATCH', + request_validator=self.api.parameter_body_validator, + request_models={'application/json': self.api_model.patch_license_investigation_request_model}, + method_responses=[ + MethodResponse( + status_code='200', + response_models={'application/json': self.api_model.message_response_model}, + ), + ], + integration=LambdaIntegration(investigation_handler, timeout=Duration.seconds(29)), request_parameters={'method.request.header.Authorization': True}, authorization_type=method_options.authorization_type, authorizer=method_options.authorizer, diff --git a/backend/compact-connect/stacks/notification_stack.py b/backend/compact-connect/stacks/notification_stack.py index 5bfffff23..d7dd3bee0 100644 --- a/backend/compact-connect/stacks/notification_stack.py +++ b/backend/compact-connect/stacks/notification_stack.py @@ -44,6 +44,10 @@ def __init__( self._add_license_encumbrance_lifting_notification_listener(persistent_stack, data_event_bus) self._add_privilege_encumbrance_notification_listener(persistent_stack, data_event_bus) self._add_privilege_encumbrance_lifting_notification_listener(persistent_stack, data_event_bus) + self._add_license_investigation_notification_listener(persistent_stack, data_event_bus) + self._add_license_investigation_closed_notification_listener(persistent_stack, data_event_bus) + self._add_privilege_investigation_notification_listener(persistent_stack, data_event_bus) + self._add_privilege_investigation_closed_notification_listener(persistent_stack, data_event_bus) def _add_privilege_purchase_notification_chain( self, persistent_stack: ps.PersistentStack, data_event_bus: IEventBus @@ -257,3 +261,55 @@ def _add_privilege_encumbrance_lifting_notification_listener( persistent_stack=persistent_stack, data_event_bus=data_event_bus, ) + + def _add_license_investigation_notification_listener( + self, persistent_stack: ps.PersistentStack, data_event_bus: EventBus + ): + """Add the license investigation notification listener lambda, queues, and event rules.""" + self._add_emailer_event_listener( + construct_id_prefix='LicenseInvestigationNotificationListener', + index='investigation_events.py', + handler='license_investigation_notification_listener', + listener_detail_type='license.investigation', + persistent_stack=persistent_stack, + data_event_bus=data_event_bus, + ) + + def _add_license_investigation_closed_notification_listener( + self, persistent_stack: ps.PersistentStack, data_event_bus: EventBus + ): + """Add the license investigation closed notification listener lambda, queues, and event rules.""" + self._add_emailer_event_listener( + construct_id_prefix='LicenseInvestigationClosedNotificationListener', + index='investigation_events.py', + handler='license_investigation_closed_notification_listener', + listener_detail_type='license.investigationClosed', + persistent_stack=persistent_stack, + data_event_bus=data_event_bus, + ) + + def _add_privilege_investigation_notification_listener( + self, persistent_stack: ps.PersistentStack, data_event_bus: EventBus + ): + """Add the privilege investigation notification listener lambda, queues, and event rules.""" + self._add_emailer_event_listener( + construct_id_prefix='PrivilegeInvestigationNotificationListener', + index='investigation_events.py', + handler='privilege_investigation_notification_listener', + listener_detail_type='privilege.investigation', + persistent_stack=persistent_stack, + data_event_bus=data_event_bus, + ) + + def _add_privilege_investigation_closed_notification_listener( + self, persistent_stack: ps.PersistentStack, data_event_bus: EventBus + ): + """Add the privilege investigation closed notification listener lambda, queues, and event rules.""" + self._add_emailer_event_listener( + construct_id_prefix='PrivilegeInvestigationClosedNotificationListener', + index='investigation_events.py', + handler='privilege_investigation_closed_notification_listener', + listener_detail_type='privilege.investigationClosed', + persistent_stack=persistent_stack, + data_event_bus=data_event_bus, + ) diff --git a/backend/compact-connect/tests/app/base.py b/backend/compact-connect/tests/app/base.py index 06d59ed8f..7d6ff3a54 100644 --- a/backend/compact-connect/tests/app/base.py +++ b/backend/compact-connect/tests/app/base.py @@ -65,9 +65,23 @@ def setUpClass(cls): # pylint: disable=invalid-name """ We build the app once per TestCase, to save compute time in the test suite """ + cls._overwrite_snapshots = False + cls.set_overwrite_snapshots() + cls.context = cls.get_context() cls.app = _app_synthesizer.get_app(cls.context) + @classmethod + def set_overwrite_snapshots(cls): + """ + Allow environment variable to force snapshot comparisons to overwrite the snapshot + + ``` + OVERWRITE_SNAPSHOTS=true pytest tests + ``` + """ + cls._overwrite_snapshots = os.environ.get('OVERWRITE_SNAPSHOTS', 'false').lower() == 'true' + def test_no_compact_jurisdiction_name_clash(self): """ Because compact and jurisdiction abbreviations share space in access token scopes, we need to ensure that @@ -607,6 +621,9 @@ def compare_snapshot(self, actual: Mapping | list, snapshot_name: str, overwrite Compare the actual dictionary to the snapshot with the given name. If overwrite_snapshot is True, overwrite the snapshot with the actual data. """ + # Let class attribute force true + overwrite_snapshot = overwrite_snapshot or self._overwrite_snapshots + snapshot_path = os.path.join('tests', 'resources', 'snapshots', f'{snapshot_name}.json') if os.path.exists(snapshot_path): diff --git a/backend/compact-connect/tests/app/test_api/test_investigation_api.py b/backend/compact-connect/tests/app/test_api/test_investigation_api.py new file mode 100644 index 000000000..367e45217 --- /dev/null +++ b/backend/compact-connect/tests/app/test_api/test_investigation_api.py @@ -0,0 +1,463 @@ +from aws_cdk.assertions import Capture, Template +from aws_cdk.aws_apigateway import CfnMethod, CfnModel, CfnResource +from aws_cdk.aws_lambda import CfnFunction + +from tests.app.test_api import TestApi + + +class TestInvestigationApi(TestApi): + """ + These tests are focused on checking that the API endpoints for investigation functionality + are configured correctly. + + When adding or modifying API resources under /investigation/, a test should be added to ensure that the + resource is created as expected. The pattern for these tests includes the following checks: + 1. The path and parent id of the API Gateway resource matches expected values. + 2. If the resource has a lambda function associated with it, the function is present with the expected + module and function. + 3. Check the methods associated with the resource, ensuring they are all present and have the correct handlers. + 4. Ensure the request and response models for the endpoint are present and match the expected schemas. + """ + + def _get_privilege_investigation_resource_id(self, api_stack_template, api_stack): + """Helper method to get the privilege investigation resource ID by traversing the resource hierarchy.""" + license_type_param_logical_id = self._get_privilege_license_type_param_resource_id( + api_stack_template, api_stack + ) + + investigation_resource_logical_ids = api_stack_template.find_resources( + type=CfnResource.CFN_RESOURCE_TYPE_NAME, + props={ + 'Properties': { + 'ParentId': {'Ref': license_type_param_logical_id}, + 'PathPart': 'investigation', + } + }, + ) + self.assertEqual(len(investigation_resource_logical_ids), 1) + return next(key for key in investigation_resource_logical_ids.keys()) + + def _get_privilege_investigation_id_resource_id(self, api_stack_template, api_stack): + """Helper method to get the privilege investigation {investigationId} resource ID.""" + investigation_resource_logical_id = self._get_privilege_investigation_resource_id(api_stack_template, api_stack) + + investigation_id_resource_logical_ids = api_stack_template.find_resources( + type=CfnResource.CFN_RESOURCE_TYPE_NAME, + props={ + 'Properties': { + 'ParentId': {'Ref': investigation_resource_logical_id}, + 'PathPart': '{investigationId}', + } + }, + ) + self.assertEqual(len(investigation_id_resource_logical_ids), 1) + return next(key for key in investigation_id_resource_logical_ids.keys()) + + def _get_license_investigation_resource_id(self, api_stack_template, api_stack): + """Helper method to get the license investigation resource ID by traversing the resource hierarchy.""" + license_type_param_logical_id = self._get_license_license_type_param_resource_id(api_stack_template, api_stack) + + investigation_resource_logical_ids = api_stack_template.find_resources( + type=CfnResource.CFN_RESOURCE_TYPE_NAME, + props={ + 'Properties': { + 'ParentId': {'Ref': license_type_param_logical_id}, + 'PathPart': 'investigation', + } + }, + ) + self.assertEqual(len(investigation_resource_logical_ids), 1) + return next(key for key in investigation_resource_logical_ids.keys()) + + def _get_license_investigation_id_resource_id(self, api_stack_template, api_stack): + """Helper method to get the license investigation {investigationId} resource ID.""" + investigation_resource_logical_id = self._get_license_investigation_resource_id(api_stack_template, api_stack) + + investigation_id_resource_logical_ids = api_stack_template.find_resources( + type=CfnResource.CFN_RESOURCE_TYPE_NAME, + props={ + 'Properties': { + 'ParentId': {'Ref': investigation_resource_logical_id}, + 'PathPart': '{investigationId}', + } + }, + ) + self.assertEqual(len(investigation_id_resource_logical_ids), 1) + return next(key for key in investigation_id_resource_logical_ids.keys()) + + def _get_privilege_license_type_param_resource_id(self, api_stack_template, api_stack): + """ + Helper method to get the privilege {licenseType} parameter resource ID by traversing the resource hierarchy. + """ + provider_resource = api_stack.api.v1_api.provider_management.provider_resource.node.default_child + privileges_logical_id = api_stack_template.find_resources( + type=CfnResource.CFN_RESOURCE_TYPE_NAME, + props={ + 'Properties': { + 'ParentId': {'Ref': api_stack.get_logical_id(provider_resource)}, + 'PathPart': 'privileges', + } + }, + ) + self.assertEqual(len(privileges_logical_id), 1) + privileges_logical_id = next(key for key in privileges_logical_id.keys()) + + jurisdiction_logical_id = api_stack_template.find_resources( + type=CfnResource.CFN_RESOURCE_TYPE_NAME, + props={ + 'Properties': { + 'ParentId': {'Ref': privileges_logical_id}, + 'PathPart': 'jurisdiction', + } + }, + ) + self.assertEqual(len(jurisdiction_logical_id), 1) + jurisdiction_logical_id = next(key for key in jurisdiction_logical_id.keys()) + + jurisdiction_param_logical_id = api_stack_template.find_resources( + type=CfnResource.CFN_RESOURCE_TYPE_NAME, + props={ + 'Properties': { + 'ParentId': {'Ref': jurisdiction_logical_id}, + 'PathPart': '{jurisdiction}', + } + }, + ) + self.assertEqual(len(jurisdiction_param_logical_id), 1) + jurisdiction_param_logical_id = next(key for key in jurisdiction_param_logical_id.keys()) + + license_type_logical_id = api_stack_template.find_resources( + type=CfnResource.CFN_RESOURCE_TYPE_NAME, + props={ + 'Properties': { + 'ParentId': {'Ref': jurisdiction_param_logical_id}, + 'PathPart': 'licenseType', + } + }, + ) + self.assertEqual(len(license_type_logical_id), 1) + license_type_logical_id = next(key for key in license_type_logical_id.keys()) + + license_type_param_logical_id = api_stack_template.find_resources( + type=CfnResource.CFN_RESOURCE_TYPE_NAME, + props={ + 'Properties': { + 'ParentId': {'Ref': license_type_logical_id}, + 'PathPart': '{licenseType}', + } + }, + ) + self.assertEqual(len(license_type_param_logical_id), 1) + return next(key for key in license_type_param_logical_id.keys()) + + def _get_license_license_type_param_resource_id(self, api_stack_template, api_stack): + """Helper method to get the license {licenseType} parameter resource ID by traversing the resource hierarchy.""" + provider_resource = api_stack.api.v1_api.provider_management.provider_resource.node.default_child + licenses_logical_id = api_stack_template.find_resources( + type=CfnResource.CFN_RESOURCE_TYPE_NAME, + props={ + 'Properties': { + 'ParentId': {'Ref': api_stack.get_logical_id(provider_resource)}, + 'PathPart': 'licenses', + } + }, + ) + self.assertEqual(len(licenses_logical_id), 1) + licenses_logical_id = next(key for key in licenses_logical_id.keys()) + + jurisdiction_logical_id = api_stack_template.find_resources( + type=CfnResource.CFN_RESOURCE_TYPE_NAME, + props={ + 'Properties': { + 'ParentId': {'Ref': licenses_logical_id}, + 'PathPart': 'jurisdiction', + } + }, + ) + self.assertEqual(len(jurisdiction_logical_id), 1) + jurisdiction_logical_id = next(key for key in jurisdiction_logical_id.keys()) + + jurisdiction_param_logical_id = api_stack_template.find_resources( + type=CfnResource.CFN_RESOURCE_TYPE_NAME, + props={ + 'Properties': { + 'ParentId': {'Ref': jurisdiction_logical_id}, + 'PathPart': '{jurisdiction}', + } + }, + ) + self.assertEqual(len(jurisdiction_param_logical_id), 1) + jurisdiction_param_logical_id = next(key for key in jurisdiction_param_logical_id.keys()) + + license_type_logical_id = api_stack_template.find_resources( + type=CfnResource.CFN_RESOURCE_TYPE_NAME, + props={ + 'Properties': { + 'ParentId': {'Ref': jurisdiction_param_logical_id}, + 'PathPart': 'licenseType', + } + }, + ) + self.assertEqual(len(license_type_logical_id), 1) + license_type_logical_id = next(key for key in license_type_logical_id.keys()) + + license_type_param_logical_id = api_stack_template.find_resources( + type=CfnResource.CFN_RESOURCE_TYPE_NAME, + props={ + 'Properties': { + 'ParentId': {'Ref': license_type_logical_id}, + 'PathPart': '{licenseType}', + } + }, + ) + self.assertEqual(len(license_type_param_logical_id), 1) + return next(key for key in license_type_param_logical_id.keys()) + + def test_synth_generates_privilege_investigation_resource(self): + """Test that the privilege investigation resource is created correctly.""" + api_stack = self.app.sandbox_backend_stage.api_stack + api_stack_template = Template.from_stack(api_stack) + + # Ensure the resource is created with expected path + api_stack_template.has_resource_properties( + type=CfnResource.CFN_RESOURCE_TYPE_NAME, + props={ + 'ParentId': {'Ref': self._get_privilege_license_type_param_resource_id(api_stack_template, api_stack)}, + 'PathPart': 'investigation', + }, + ) + + def test_synth_generates_privilege_investigation_id_resource(self): + """Test that the privilege investigation {investigationId} resource is created correctly.""" + api_stack = self.app.sandbox_backend_stage.api_stack + api_stack_template = Template.from_stack(api_stack) + + # Ensure the resource is created with expected path + api_stack_template.has_resource_properties( + type=CfnResource.CFN_RESOURCE_TYPE_NAME, + props={ + 'ParentId': {'Ref': self._get_privilege_investigation_resource_id(api_stack_template, api_stack)}, + 'PathPart': '{investigationId}', + }, + ) + + def test_synth_generates_license_investigation_resource(self): + """Test that the license investigation resource is created correctly.""" + api_stack = self.app.sandbox_backend_stage.api_stack + api_stack_template = Template.from_stack(api_stack) + + # Ensure the resource is created with expected path + api_stack_template.has_resource_properties( + type=CfnResource.CFN_RESOURCE_TYPE_NAME, + props={ + 'ParentId': {'Ref': self._get_license_license_type_param_resource_id(api_stack_template, api_stack)}, + 'PathPart': 'investigation', + }, + ) + + def test_synth_generates_license_investigation_id_resource(self): + """Test that the license investigation {investigationId} resource is created correctly.""" + api_stack = self.app.sandbox_backend_stage.api_stack + api_stack_template = Template.from_stack(api_stack) + + # Ensure the resource is created with expected path + api_stack_template.has_resource_properties( + type=CfnResource.CFN_RESOURCE_TYPE_NAME, + props={ + 'ParentId': {'Ref': self._get_license_investigation_resource_id(api_stack_template, api_stack)}, + 'PathPart': '{investigationId}', + }, + ) + + def test_synth_generates_privilege_investigation_handler(self): + """Test that the privilege investigation handler lambda is created correctly.""" + api_lambda_stack = self.app.sandbox_backend_stage.api_lambda_stack + api_lambda_stack_template = Template.from_stack(api_lambda_stack) + + # Ensure the lambda is created with expected code path + investigation_handler = TestApi.get_resource_properties_by_logical_id( + api_lambda_stack.get_logical_id( + api_lambda_stack.provider_management_lambdas.provider_investigation_handler.node.default_child + ), + api_lambda_stack_template.find_resources(CfnFunction.CFN_RESOURCE_TYPE_NAME), + ) + + self.assertEqual(investigation_handler['Handler'], 'handlers.investigation.investigation_handler') + + def test_synth_generates_post_privilege_investigation_endpoint(self): + """Test that the POST privilege investigation endpoint is configured correctly.""" + api_stack = self.app.sandbox_backend_stage.api_stack + api_stack_template = Template.from_stack(api_stack) + api_lambda_stack = self.app.sandbox_backend_stage.api_lambda_stack + api_lambda_stack_template = Template.from_stack(api_lambda_stack) + + # Ensure the POST method is configured correctly (no request model required) + api_stack_template.has_resource_properties( + type=CfnMethod.CFN_RESOURCE_TYPE_NAME, + props={ + 'HttpMethod': 'POST', + 'AuthorizationType': 'COGNITO_USER_POOLS', + 'AuthorizerId': { + 'Ref': api_stack.get_logical_id(api_stack.api.staff_users_authorizer.node.default_child), + }, + 'ResourceId': {'Ref': self._get_privilege_investigation_resource_id(api_stack_template, api_stack)}, + 'Integration': TestApi.generate_expected_integration_object_for_imported_lambda( + api_lambda_stack, + api_lambda_stack_template, + api_lambda_stack.provider_management_lambdas.provider_investigation_handler, + ), + 'MethodResponses': [ + { + 'ResponseModels': { + 'application/json': { + 'Ref': api_stack.get_logical_id( + api_stack.api.v1_api.api_model.message_response_model.node.default_child + ) + } + }, + 'StatusCode': '200', + }, + ], + }, + ) + + def test_synth_generates_patch_privilege_investigation_endpoint(self): + """Test that the PATCH privilege investigation endpoint is configured correctly.""" + api_stack = self.app.sandbox_backend_stage.api_stack + api_stack_template = Template.from_stack(api_stack) + api_lambda_stack = self.app.sandbox_backend_stage.api_lambda_stack + api_lambda_stack_template = Template.from_stack(api_lambda_stack) + + request_model_logical_id_capture = Capture() + + # Ensure the PATCH method is configured correctly + api_stack_template.has_resource_properties( + type=CfnMethod.CFN_RESOURCE_TYPE_NAME, + props={ + 'HttpMethod': 'PATCH', + 'AuthorizationType': 'COGNITO_USER_POOLS', + 'AuthorizerId': { + 'Ref': api_stack.get_logical_id(api_stack.api.staff_users_authorizer.node.default_child), + }, + 'ResourceId': {'Ref': self._get_privilege_investigation_id_resource_id(api_stack_template, api_stack)}, + 'Integration': TestApi.generate_expected_integration_object_for_imported_lambda( + api_lambda_stack, + api_lambda_stack_template, + api_lambda_stack.provider_management_lambdas.provider_investigation_handler, + ), + 'RequestModels': {'application/json': {'Ref': request_model_logical_id_capture}}, + 'MethodResponses': [ + { + 'ResponseModels': { + 'application/json': { + 'Ref': api_stack.get_logical_id( + api_stack.api.v1_api.api_model.message_response_model.node.default_child + ) + } + }, + 'StatusCode': '200', + }, + ], + }, + ) + + # Verify the request model matches expected schema + patch_privilege_investigation_request_model = TestApi.get_resource_properties_by_logical_id( + request_model_logical_id_capture.as_string(), + api_stack_template.find_resources(CfnModel.CFN_RESOURCE_TYPE_NAME), + ) + + self.compare_snapshot( + patch_privilege_investigation_request_model['Schema'], + 'PATCH_PRIVILEGE_INVESTIGATION_REQUEST_SCHEMA', + overwrite_snapshot=False, + ) + + def test_synth_generates_post_license_investigation_endpoint(self): + """Test that the POST license investigation endpoint is configured correctly.""" + api_stack = self.app.sandbox_backend_stage.api_stack + api_stack_template = Template.from_stack(api_stack) + api_lambda_stack = self.app.sandbox_backend_stage.api_lambda_stack + api_lambda_stack_template = Template.from_stack(api_lambda_stack) + + # Ensure the POST method is configured correctly (no request model required) + api_stack_template.has_resource_properties( + type=CfnMethod.CFN_RESOURCE_TYPE_NAME, + props={ + 'HttpMethod': 'POST', + 'AuthorizationType': 'COGNITO_USER_POOLS', + 'AuthorizerId': { + 'Ref': api_stack.get_logical_id(api_stack.api.staff_users_authorizer.node.default_child), + }, + 'ResourceId': {'Ref': self._get_license_investigation_resource_id(api_stack_template, api_stack)}, + 'Integration': TestApi.generate_expected_integration_object_for_imported_lambda( + api_lambda_stack, + api_lambda_stack_template, + api_lambda_stack.provider_management_lambdas.provider_investigation_handler, + ), + 'MethodResponses': [ + { + 'ResponseModels': { + 'application/json': { + 'Ref': api_stack.get_logical_id( + api_stack.api.v1_api.api_model.message_response_model.node.default_child + ) + } + }, + 'StatusCode': '200', + }, + ], + }, + ) + + def test_synth_generates_patch_license_investigation_endpoint(self): + """Test that the PATCH license investigation endpoint is configured correctly.""" + api_stack = self.app.sandbox_backend_stage.api_stack + api_stack_template = Template.from_stack(api_stack) + api_lambda_stack = self.app.sandbox_backend_stage.api_lambda_stack + api_lambda_stack_template = Template.from_stack(api_lambda_stack) + + request_model_logical_id_capture = Capture() + + # Ensure the PATCH method is configured correctly + api_stack_template.has_resource_properties( + type=CfnMethod.CFN_RESOURCE_TYPE_NAME, + props={ + 'HttpMethod': 'PATCH', + 'AuthorizationType': 'COGNITO_USER_POOLS', + 'AuthorizerId': { + 'Ref': api_stack.get_logical_id(api_stack.api.staff_users_authorizer.node.default_child), + }, + 'ResourceId': {'Ref': self._get_license_investigation_id_resource_id(api_stack_template, api_stack)}, + 'Integration': TestApi.generate_expected_integration_object_for_imported_lambda( + api_lambda_stack, + api_lambda_stack_template, + api_lambda_stack.provider_management_lambdas.provider_investigation_handler, + ), + 'RequestModels': {'application/json': {'Ref': request_model_logical_id_capture}}, + 'MethodResponses': [ + { + 'ResponseModels': { + 'application/json': { + 'Ref': api_stack.get_logical_id( + api_stack.api.v1_api.api_model.message_response_model.node.default_child + ) + } + }, + 'StatusCode': '200', + }, + ], + }, + ) + + # Verify the request model matches expected schema + patch_license_investigation_request_model = TestApi.get_resource_properties_by_logical_id( + request_model_logical_id_capture.as_string(), + api_stack_template.find_resources(CfnModel.CFN_RESOURCE_TYPE_NAME), + ) + + self.compare_snapshot( + patch_license_investigation_request_model['Schema'], + 'PATCH_LICENSE_INVESTIGATION_REQUEST_SCHEMA', + overwrite_snapshot=False, + ) diff --git a/backend/compact-connect/tests/app/test_notification_stack.py b/backend/compact-connect/tests/app/test_notification_stack.py index 3c8038f26..71785982d 100644 --- a/backend/compact-connect/tests/app/test_notification_stack.py +++ b/backend/compact-connect/tests/app/test_notification_stack.py @@ -564,3 +564,330 @@ def test_privilege_encumbrance_lifting_notification_listener_resources_created(s }, event_source_mapping, ) + + def test_license_investigation_notification_resources_created(self): + """ + Test that the license investigation notification listener lambda is added with a SQS queue + and an event bridge event rule that listens for 'license.investigation' detail types. + """ + notification_stack = self.app.sandbox_backend_stage.notification_stack + notification_template = Template.from_stack(notification_stack) + + # Verify the lambda function is created + license_investigation_handler_logical_id = notification_stack.get_logical_id( + notification_stack.event_processors[ + 'LicenseInvestigationNotificationListener' + ].queue_processor.process_function.node.default_child + ) + license_investigation_handler = TestNotificationStack.get_resource_properties_by_logical_id( + license_investigation_handler_logical_id, + resources=notification_template.find_resources(CfnFunction.CFN_RESOURCE_TYPE_NAME), + ) + + self.assertEqual( + 'handlers.investigation_events.license_investigation_notification_listener', + license_investigation_handler['Handler'], + ) + + # Verify SQS queue is created for the license investigation notification listener + investigation_listener_queue_logical_id = notification_stack.get_logical_id( + notification_stack.event_processors[ + 'LicenseInvestigationNotificationListener' + ].queue_processor.queue.node.default_child + ) + license_investigation_listener_queue = TestNotificationStack.get_resource_properties_by_logical_id( + investigation_listener_queue_logical_id, + resources=notification_template.find_resources(CfnQueue.CFN_RESOURCE_TYPE_NAME), + ) + + dlq_logical_id = notification_stack.get_logical_id( + notification_stack.event_processors[ + 'LicenseInvestigationNotificationListener' + ].queue_processor.dlq.node.default_child + ) + + # remove dynamic field + del license_investigation_listener_queue['KmsMasterKeyId'] + + self.assertEqual( + { + 'MessageRetentionPeriod': 43200, + 'RedrivePolicy': {'deadLetterTargetArn': {'Fn::GetAtt': [dlq_logical_id, 'Arn']}, 'maxReceiveCount': 3}, + 'VisibilityTimeout': 300, + }, + license_investigation_listener_queue, + ) + + # Verify EventBridge rule is created with correct detail type + license_investigation_rule = TestNotificationStack.get_resource_properties_by_logical_id( + notification_stack.get_logical_id( + notification_stack.event_processors[ + 'LicenseInvestigationNotificationListener' + ].event_rule.node.default_child + ), + resources=notification_template.find_resources(CfnRule.CFN_RESOURCE_TYPE_NAME), + ) + + self.assertEqual( + { + 'EventBusName': { + 'Fn::Select': [ + 1, + { + 'Fn::Split': [ + '/', + {'Fn::Select': [5, {'Fn::Split': [':', {'Ref': 'DataEventBusArnParameterParameter'}]}]}, + ] + }, + ] + }, + 'EventPattern': {'detail-type': ['license.investigation']}, + 'State': 'ENABLED', + 'Targets': [ + { + 'Arn': {'Fn::GetAtt': [investigation_listener_queue_logical_id, 'Arn']}, + 'DeadLetterConfig': {'Arn': {'Fn::GetAtt': [dlq_logical_id, 'Arn']}}, + 'Id': 'Target0', + } + ], + }, + license_investigation_rule, + ) + + # Verify event source mapping is created + event_source_mapping = TestNotificationStack.get_resource_properties_by_logical_id( + notification_stack.get_logical_id( + notification_stack.event_processors[ + 'LicenseInvestigationNotificationListener' + ].queue_processor.event_source_mapping.node.default_child + ), + resources=notification_template.find_resources(CfnEventSourceMapping.CFN_RESOURCE_TYPE_NAME), + ) + self.assertEqual( + { + 'BatchSize': 10, + 'EventSourceArn': {'Fn::GetAtt': [investigation_listener_queue_logical_id, 'Arn']}, + 'FunctionName': {'Ref': license_investigation_handler_logical_id}, + 'FunctionResponseTypes': ['ReportBatchItemFailures'], + 'MaximumBatchingWindowInSeconds': 15, + }, + event_source_mapping, + ) + + def test_license_investigation_closed_notification_resources_created(self): + """ + Test that the license investigation closed notification listener lambda is added with a SQS queue + and an event bridge event rule that listens for 'license.investigationClosed' detail types. + """ + notification_stack = self.app.sandbox_backend_stage.notification_stack + notification_template = Template.from_stack(notification_stack) + + # Verify the lambda function is created + license_investigation_closed_handler_logical_id = notification_stack.get_logical_id( + notification_stack.event_processors[ + 'LicenseInvestigationClosedNotificationListener' + ].queue_processor.process_function.node.default_child + ) + license_investigation_closed_handler = TestNotificationStack.get_resource_properties_by_logical_id( + license_investigation_closed_handler_logical_id, + resources=notification_template.find_resources(CfnFunction.CFN_RESOURCE_TYPE_NAME), + ) + + self.assertEqual( + 'handlers.investigation_events.license_investigation_closed_notification_listener', + license_investigation_closed_handler['Handler'], + ) + + # Verify EventBridge rule is created with correct detail type + license_investigation_closed_rule = TestNotificationStack.get_resource_properties_by_logical_id( + notification_stack.get_logical_id( + notification_stack.event_processors[ + 'LicenseInvestigationClosedNotificationListener' + ].event_rule.node.default_child + ), + resources=notification_template.find_resources(CfnRule.CFN_RESOURCE_TYPE_NAME), + ) + + # Get the queue and DLQ logical IDs for the targets + investigation_closed_listener_queue_logical_id = notification_stack.get_logical_id( + notification_stack.event_processors[ + 'LicenseInvestigationClosedNotificationListener' + ].queue_processor.queue.node.default_child + ) + investigation_closed_dlq_logical_id = notification_stack.get_logical_id( + notification_stack.event_processors[ + 'LicenseInvestigationClosedNotificationListener' + ].queue_processor.dlq.node.default_child + ) + + self.assertEqual( + { + 'EventBusName': { + 'Fn::Select': [ + 1, + { + 'Fn::Split': [ + '/', + {'Fn::Select': [5, {'Fn::Split': [':', {'Ref': 'DataEventBusArnParameterParameter'}]}]}, + ] + }, + ] + }, + 'EventPattern': {'detail-type': ['license.investigationClosed']}, + 'State': 'ENABLED', + 'Targets': [ + { + 'Arn': {'Fn::GetAtt': [investigation_closed_listener_queue_logical_id, 'Arn']}, + 'DeadLetterConfig': {'Arn': {'Fn::GetAtt': [investigation_closed_dlq_logical_id, 'Arn']}}, + 'Id': 'Target0', + } + ], + }, + license_investigation_closed_rule, + ) + + def test_privilege_investigation_notification_resources_created(self): + """ + Test that the privilege investigation notification listener lambda is added with a SQS queue + and an event bridge event rule that listens for 'privilege.investigation' detail types. + """ + notification_stack = self.app.sandbox_backend_stage.notification_stack + notification_template = Template.from_stack(notification_stack) + + # Verify the lambda function is created + privilege_investigation_handler_logical_id = notification_stack.get_logical_id( + notification_stack.event_processors[ + 'PrivilegeInvestigationNotificationListener' + ].queue_processor.process_function.node.default_child + ) + privilege_investigation_handler = TestNotificationStack.get_resource_properties_by_logical_id( + privilege_investigation_handler_logical_id, + resources=notification_template.find_resources(CfnFunction.CFN_RESOURCE_TYPE_NAME), + ) + + self.assertEqual( + 'handlers.investigation_events.privilege_investigation_notification_listener', + privilege_investigation_handler['Handler'], + ) + + # Verify EventBridge rule is created with correct detail type + privilege_investigation_rule = TestNotificationStack.get_resource_properties_by_logical_id( + notification_stack.get_logical_id( + notification_stack.event_processors[ + 'PrivilegeInvestigationNotificationListener' + ].event_rule.node.default_child + ), + resources=notification_template.find_resources(CfnRule.CFN_RESOURCE_TYPE_NAME), + ) + + # Get the queue and DLQ logical IDs for the targets + privilege_investigation_listener_queue_logical_id = notification_stack.get_logical_id( + notification_stack.event_processors[ + 'PrivilegeInvestigationNotificationListener' + ].queue_processor.queue.node.default_child + ) + privilege_investigation_dlq_logical_id = notification_stack.get_logical_id( + notification_stack.event_processors[ + 'PrivilegeInvestigationNotificationListener' + ].queue_processor.dlq.node.default_child + ) + + self.assertEqual( + { + 'EventBusName': { + 'Fn::Select': [ + 1, + { + 'Fn::Split': [ + '/', + {'Fn::Select': [5, {'Fn::Split': [':', {'Ref': 'DataEventBusArnParameterParameter'}]}]}, + ] + }, + ] + }, + 'EventPattern': {'detail-type': ['privilege.investigation']}, + 'State': 'ENABLED', + 'Targets': [ + { + 'Arn': {'Fn::GetAtt': [privilege_investigation_listener_queue_logical_id, 'Arn']}, + 'DeadLetterConfig': {'Arn': {'Fn::GetAtt': [privilege_investigation_dlq_logical_id, 'Arn']}}, + 'Id': 'Target0', + } + ], + }, + privilege_investigation_rule, + ) + + def test_privilege_investigation_closed_notification_resources_created(self): + """ + Test that the privilege investigation closed notification listener lambda is added with a SQS queue + and an event bridge event rule that listens for 'privilege.investigationClosed' detail types. + """ + notification_stack = self.app.sandbox_backend_stage.notification_stack + notification_template = Template.from_stack(notification_stack) + + # Verify the lambda function is created + privilege_investigation_closed_handler_logical_id = notification_stack.get_logical_id( + notification_stack.event_processors[ + 'PrivilegeInvestigationClosedNotificationListener' + ].queue_processor.process_function.node.default_child + ) + privilege_investigation_closed_handler = TestNotificationStack.get_resource_properties_by_logical_id( + privilege_investigation_closed_handler_logical_id, + resources=notification_template.find_resources(CfnFunction.CFN_RESOURCE_TYPE_NAME), + ) + + self.assertEqual( + 'handlers.investigation_events.privilege_investigation_closed_notification_listener', + privilege_investigation_closed_handler['Handler'], + ) + + # Verify EventBridge rule is created with correct detail type + privilege_investigation_closed_rule = TestNotificationStack.get_resource_properties_by_logical_id( + notification_stack.get_logical_id( + notification_stack.event_processors[ + 'PrivilegeInvestigationClosedNotificationListener' + ].event_rule.node.default_child + ), + resources=notification_template.find_resources(CfnRule.CFN_RESOURCE_TYPE_NAME), + ) + + # Get the queue and DLQ logical IDs for the targets + privilege_investigation_closed_listener_queue_logical_id = notification_stack.get_logical_id( + notification_stack.event_processors[ + 'PrivilegeInvestigationClosedNotificationListener' + ].queue_processor.queue.node.default_child + ) + privilege_investigation_closed_dlq_logical_id = notification_stack.get_logical_id( + notification_stack.event_processors[ + 'PrivilegeInvestigationClosedNotificationListener' + ].queue_processor.dlq.node.default_child + ) + + self.assertEqual( + { + 'EventBusName': { + 'Fn::Select': [ + 1, + { + 'Fn::Split': [ + '/', + {'Fn::Select': [5, {'Fn::Split': [':', {'Ref': 'DataEventBusArnParameterParameter'}]}]}, + ] + }, + ] + }, + 'EventPattern': {'detail-type': ['privilege.investigationClosed']}, + 'State': 'ENABLED', + 'Targets': [ + { + 'Arn': {'Fn::GetAtt': [privilege_investigation_closed_listener_queue_logical_id, 'Arn']}, + 'DeadLetterConfig': { + 'Arn': {'Fn::GetAtt': [privilege_investigation_closed_dlq_logical_id, 'Arn']} + }, + 'Id': 'Target0', + } + ], + }, + privilege_investigation_closed_rule, + ) 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 b3fe43b29..c6b5091df 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 @@ -90,8 +90,7 @@ "type": "string" }, "dateOfUpdate": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "format": "date-time", "type": "string" }, "licenseStatus": { @@ -228,8 +227,7 @@ "type": "string" }, "dateOfUpdate": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "format": "date-time", "type": "string" }, "previous": { @@ -618,8 +616,7 @@ "type": "string" }, "dateOfUpdate": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "format": "date-time", "type": "string" }, "encumbranceType": { @@ -657,6 +654,125 @@ }, "type": "array" }, + "investigations": { + "items": { + "properties": { + "type": { + "enum": [ + "investigation" + ], + "type": "string" + }, + "compact": { + "enum": [ + "aslp", + "octp", + "coun" + ], + "type": "string" + }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" + }, + "investigationId": { + "type": "string" + }, + "jurisdiction": { + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" + ], + "type": "string" + }, + "licenseType": { + "type": "string" + }, + "dateOfUpdate": { + "format": "date-time", + "type": "string" + }, + "creationDate": { + "format": "date-time", + "type": "string" + }, + "submittingUser": { + "type": "string" + } + }, + "required": [ + "type", + "compact", + "providerId", + "investigationId", + "jurisdiction", + "licenseType", + "dateOfUpdate", + "creationDate", + "submittingUser" + ], + "type": "object" + }, + "type": "array" + }, + "investigationStatus": { + "description": "Status indicating if the license is under investigation", + "enum": [ + "underInvestigation" + ], + "type": "string" + }, "npi": { "pattern": "^[0-9]{10}$", "type": "string" @@ -880,8 +996,7 @@ "type": "string" }, "dateOfUpdate": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "format": "date-time", "type": "string" }, "previous": { @@ -978,8 +1093,7 @@ "type": "string" }, "dateOfUpdate": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "format": "date-time", "type": "string" }, "compactTransactionId": { @@ -1188,8 +1302,7 @@ "type": "string" }, "dateOfUpdate": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "format": "date-time", "type": "string" }, "compactTransactionId": { @@ -1430,8 +1543,7 @@ "type": "string" }, "dateOfUpdate": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "format": "date-time", "type": "string" }, "encumbranceType": { @@ -1469,6 +1581,125 @@ }, "type": "array" }, + "investigations": { + "items": { + "properties": { + "type": { + "enum": [ + "investigation" + ], + "type": "string" + }, + "compact": { + "enum": [ + "aslp", + "octp", + "coun" + ], + "type": "string" + }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" + }, + "investigationId": { + "type": "string" + }, + "jurisdiction": { + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" + ], + "type": "string" + }, + "licenseType": { + "type": "string" + }, + "dateOfUpdate": { + "format": "date-time", + "type": "string" + }, + "creationDate": { + "format": "date-time", + "type": "string" + }, + "submittingUser": { + "type": "string" + } + }, + "required": [ + "type", + "compact", + "providerId", + "investigationId", + "jurisdiction", + "licenseType", + "dateOfUpdate", + "creationDate", + "submittingUser" + ], + "type": "object" + }, + "type": "array" + }, + "investigationStatus": { + "description": "Status indicating if the privilege is under investigation", + "enum": [ + "underInvestigation" + ], + "type": "string" + }, "type": { "enum": [ "privilege" @@ -1561,8 +1792,7 @@ "type": "string" }, "dateOfUpdate": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "format": "date-time", "type": "string" }, "compactTransactionId": { @@ -1696,8 +1926,7 @@ "type": "string" }, "dateOfUpdate": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "format": "date-time", "type": "string" }, "providerId": { @@ -2046,8 +2275,7 @@ "type": "string" }, "dateOfUpdate": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "format": "date-time", "type": "string" } }, diff --git a/backend/compact-connect/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json b/backend/compact-connect/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json index cf81fc545..c29d25311 100644 --- a/backend/compact-connect/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json +++ b/backend/compact-connect/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json @@ -1,5 +1,6 @@ { "additionalProperties": false, + "description": "Encumbrance data to create", "properties": { "encumbranceEffectiveDate": { "description": "The effective date of the encumbrance", diff --git a/backend/compact-connect/tests/resources/snapshots/PATCH_LICENSE_INVESTIGATION_REQUEST_SCHEMA.json b/backend/compact-connect/tests/resources/snapshots/PATCH_LICENSE_INVESTIGATION_REQUEST_SCHEMA.json new file mode 100644 index 000000000..2bf548c90 --- /dev/null +++ b/backend/compact-connect/tests/resources/snapshots/PATCH_LICENSE_INVESTIGATION_REQUEST_SCHEMA.json @@ -0,0 +1,64 @@ +{ + "properties": { + "action": { + "enum": [ + "close" + ], + "type": "string" + }, + "encumbrance": { + "additionalProperties": false, + "description": "Encumbrance data to create", + "properties": { + "encumbranceEffectiveDate": { + "description": "The effective date of the encumbrance", + "format": "date", + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string" + }, + "encumbranceType": { + "description": "The type of encumbrance", + "enum": [ + "fine", + "reprimand", + "required supervision", + "completion of continuing education", + "public reprimand", + "probation", + "injunctive action", + "suspension", + "revocation", + "denial", + "surrender of license", + "modification of previous action-extension", + "modification of previous action-reduction", + "other monitoring", + "other adjudicated action not listed" + ], + "type": "string" + }, + "clinicalPrivilegeActionCategory": { + "description": "(Deprecated) The category of clinical privilege action. Use clinicalPrivilegeActionCategories instead.", + "type": "string" + }, + "clinicalPrivilegeActionCategories": { + "description": "The categories of clinical privilege action", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "encumbranceEffectiveDate", + "encumbranceType" + ], + "type": "object" + } + }, + "required": [ + "action" + ], + "type": "object", + "$schema": "http://json-schema.org/draft-04/schema#" +} diff --git a/backend/compact-connect/tests/resources/snapshots/PATCH_PRIVILEGE_INVESTIGATION_REQUEST_SCHEMA.json b/backend/compact-connect/tests/resources/snapshots/PATCH_PRIVILEGE_INVESTIGATION_REQUEST_SCHEMA.json new file mode 100644 index 000000000..2bf548c90 --- /dev/null +++ b/backend/compact-connect/tests/resources/snapshots/PATCH_PRIVILEGE_INVESTIGATION_REQUEST_SCHEMA.json @@ -0,0 +1,64 @@ +{ + "properties": { + "action": { + "enum": [ + "close" + ], + "type": "string" + }, + "encumbrance": { + "additionalProperties": false, + "description": "Encumbrance data to create", + "properties": { + "encumbranceEffectiveDate": { + "description": "The effective date of the encumbrance", + "format": "date", + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string" + }, + "encumbranceType": { + "description": "The type of encumbrance", + "enum": [ + "fine", + "reprimand", + "required supervision", + "completion of continuing education", + "public reprimand", + "probation", + "injunctive action", + "suspension", + "revocation", + "denial", + "surrender of license", + "modification of previous action-extension", + "modification of previous action-reduction", + "other monitoring", + "other adjudicated action not listed" + ], + "type": "string" + }, + "clinicalPrivilegeActionCategory": { + "description": "(Deprecated) The category of clinical privilege action. Use clinicalPrivilegeActionCategories instead.", + "type": "string" + }, + "clinicalPrivilegeActionCategories": { + "description": "The categories of clinical privilege action", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "encumbranceEffectiveDate", + "encumbranceType" + ], + "type": "object" + } + }, + "required": [ + "action" + ], + "type": "object", + "$schema": "http://json-schema.org/draft-04/schema#" +} diff --git a/backend/compact-connect/tests/resources/snapshots/POST_PROVIDER_USERS_MILITARY_AFFILIATION_RESPONSE_SCHEMA.json b/backend/compact-connect/tests/resources/snapshots/POST_PROVIDER_USERS_MILITARY_AFFILIATION_RESPONSE_SCHEMA.json index 474bb1310..feaac7b03 100644 --- a/backend/compact-connect/tests/resources/snapshots/POST_PROVIDER_USERS_MILITARY_AFFILIATION_RESPONSE_SCHEMA.json +++ b/backend/compact-connect/tests/resources/snapshots/POST_PROVIDER_USERS_MILITARY_AFFILIATION_RESPONSE_SCHEMA.json @@ -28,8 +28,7 @@ }, "dateOfUpdate": { "description": "The date the document was last updated", - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "format": "date-time", "type": "string" }, "documentUploadFields": { diff --git a/backend/compact-connect/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json b/backend/compact-connect/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json index cf81fc545..c29d25311 100644 --- a/backend/compact-connect/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json +++ b/backend/compact-connect/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json @@ -1,5 +1,6 @@ { "additionalProperties": false, + "description": "Encumbrance data to create", "properties": { "encumbranceEffectiveDate": { "description": "The effective date of the encumbrance", 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 b3fe43b29..c6b5091df 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 @@ -90,8 +90,7 @@ "type": "string" }, "dateOfUpdate": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "format": "date-time", "type": "string" }, "licenseStatus": { @@ -228,8 +227,7 @@ "type": "string" }, "dateOfUpdate": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "format": "date-time", "type": "string" }, "previous": { @@ -618,8 +616,7 @@ "type": "string" }, "dateOfUpdate": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "format": "date-time", "type": "string" }, "encumbranceType": { @@ -657,6 +654,125 @@ }, "type": "array" }, + "investigations": { + "items": { + "properties": { + "type": { + "enum": [ + "investigation" + ], + "type": "string" + }, + "compact": { + "enum": [ + "aslp", + "octp", + "coun" + ], + "type": "string" + }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" + }, + "investigationId": { + "type": "string" + }, + "jurisdiction": { + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" + ], + "type": "string" + }, + "licenseType": { + "type": "string" + }, + "dateOfUpdate": { + "format": "date-time", + "type": "string" + }, + "creationDate": { + "format": "date-time", + "type": "string" + }, + "submittingUser": { + "type": "string" + } + }, + "required": [ + "type", + "compact", + "providerId", + "investigationId", + "jurisdiction", + "licenseType", + "dateOfUpdate", + "creationDate", + "submittingUser" + ], + "type": "object" + }, + "type": "array" + }, + "investigationStatus": { + "description": "Status indicating if the license is under investigation", + "enum": [ + "underInvestigation" + ], + "type": "string" + }, "npi": { "pattern": "^[0-9]{10}$", "type": "string" @@ -880,8 +996,7 @@ "type": "string" }, "dateOfUpdate": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "format": "date-time", "type": "string" }, "previous": { @@ -978,8 +1093,7 @@ "type": "string" }, "dateOfUpdate": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "format": "date-time", "type": "string" }, "compactTransactionId": { @@ -1188,8 +1302,7 @@ "type": "string" }, "dateOfUpdate": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "format": "date-time", "type": "string" }, "compactTransactionId": { @@ -1430,8 +1543,7 @@ "type": "string" }, "dateOfUpdate": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "format": "date-time", "type": "string" }, "encumbranceType": { @@ -1469,6 +1581,125 @@ }, "type": "array" }, + "investigations": { + "items": { + "properties": { + "type": { + "enum": [ + "investigation" + ], + "type": "string" + }, + "compact": { + "enum": [ + "aslp", + "octp", + "coun" + ], + "type": "string" + }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" + }, + "investigationId": { + "type": "string" + }, + "jurisdiction": { + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "pr", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" + ], + "type": "string" + }, + "licenseType": { + "type": "string" + }, + "dateOfUpdate": { + "format": "date-time", + "type": "string" + }, + "creationDate": { + "format": "date-time", + "type": "string" + }, + "submittingUser": { + "type": "string" + } + }, + "required": [ + "type", + "compact", + "providerId", + "investigationId", + "jurisdiction", + "licenseType", + "dateOfUpdate", + "creationDate", + "submittingUser" + ], + "type": "object" + }, + "type": "array" + }, + "investigationStatus": { + "description": "Status indicating if the privilege is under investigation", + "enum": [ + "underInvestigation" + ], + "type": "string" + }, "type": { "enum": [ "privilege" @@ -1561,8 +1792,7 @@ "type": "string" }, "dateOfUpdate": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "format": "date-time", "type": "string" }, "compactTransactionId": { @@ -1696,8 +1926,7 @@ "type": "string" }, "dateOfUpdate": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "format": "date-time", "type": "string" }, "providerId": { @@ -2046,8 +2275,7 @@ "type": "string" }, "dateOfUpdate": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "format": "date-time", "type": "string" } }, diff --git a/backend/compact-connect/tests/resources/snapshots/PUBLIC_GET_PROVIDER_RESPONSE_SCHEMA.json b/backend/compact-connect/tests/resources/snapshots/PUBLIC_GET_PROVIDER_RESPONSE_SCHEMA.json index 6c00bffb3..0f81a4344 100644 --- a/backend/compact-connect/tests/resources/snapshots/PUBLIC_GET_PROVIDER_RESPONSE_SCHEMA.json +++ b/backend/compact-connect/tests/resources/snapshots/PUBLIC_GET_PROVIDER_RESPONSE_SCHEMA.json @@ -163,8 +163,7 @@ "type": "string" }, "dateOfUpdate": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "format": "date-time", "type": "string" }, "privilegeId": { @@ -290,8 +289,7 @@ "type": "string" }, "dateOfUpdate": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "format": "date-time", "type": "string" }, "previous": { @@ -319,8 +317,7 @@ "type": "string" }, "dateOfUpdate": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "format": "date-time", "type": "string" }, "licenseJurisdiction": { @@ -421,8 +418,7 @@ "type": "string" }, "dateOfUpdate": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "format": "date-time", "type": "string" }, "licenseJurisdiction": { @@ -612,8 +608,7 @@ "type": "string" }, "dateOfUpdate": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "format": "date-time", "type": "string" } }, @@ -877,8 +872,7 @@ "type": "string" }, "dateOfUpdate": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "format": "date-time", "type": "string" } }, diff --git a/backend/compact-connect/tests/resources/snapshots/PUBLIC_QUERY_PROVIDERS_RESPONSE_SCHEMA.json b/backend/compact-connect/tests/resources/snapshots/PUBLIC_QUERY_PROVIDERS_RESPONSE_SCHEMA.json index 20a265e9c..5d1776c06 100644 --- a/backend/compact-connect/tests/resources/snapshots/PUBLIC_QUERY_PROVIDERS_RESPONSE_SCHEMA.json +++ b/backend/compact-connect/tests/resources/snapshots/PUBLIC_QUERY_PROVIDERS_RESPONSE_SCHEMA.json @@ -226,8 +226,7 @@ "type": "string" }, "dateOfUpdate": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "format": "date-time", "type": "string" } }, 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 8f868dec5..0deae9cb6 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 @@ -279,8 +279,7 @@ "type": "string" }, "dateOfUpdate": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "format": "date-time", "type": "string" } }, diff --git a/backend/compact-connect/tests/smoke/investigation_smoke_tests.py b/backend/compact-connect/tests/smoke/investigation_smoke_tests.py new file mode 100755 index 000000000..347699287 --- /dev/null +++ b/backend/compact-connect/tests/smoke/investigation_smoke_tests.py @@ -0,0 +1,490 @@ +#!/usr/bin/env python3 +""" +Smoke tests for investigation functionality. + +This script tests the end-to-end investigation workflow for both licenses and privileges, +including creating investigations and closing them through the API endpoints. +""" + +import time + +import requests +from smoke_common import ( + SmokeTestFailureException, + call_provider_users_me_endpoint, + config, + create_test_staff_user, + delete_test_staff_user, + get_all_provider_database_records, + get_license_type_abbreviation, + get_provider_user_dynamodb_table, + get_staff_user_auth_headers, + load_smoke_test_env, + logger, +) + + +def clean_investigation_records(): + """ + Clean up any existing investigation and encumbrance records for the provider to start in a clean state. + """ + logger.info('Cleaning up existing investigation and encumbrance records...') + + # Get all provider database records + all_records = get_all_provider_database_records() + + for record in all_records: + if record.get('type') == 'license' or record.get('type') == 'privilege': + if record.get('investigationStatus') == 'underInvestigation': + logger.info( + f'Removing investigation and encumbrance status from {record.get("type")} ' + f'{record.get("pk")} / {record.get("sk")}' + ) + dynamodb_table = get_provider_user_dynamodb_table() + dynamodb_table.update_item( + Key={'pk': record['pk'], 'sk': record['sk']}, + UpdateExpression='REMOVE investigationStatus, encumbranceStatus', + ) + + # Filter for investigation and encumbrance records + investigation_records = [record for record in all_records if record.get('type') == 'investigation'] + encumbrance_records = [record for record in all_records if record.get('type') == 'adverseAction'] + + # Filter for investigation and encumbrance update records + investigation_update_records = [ + record + for record in all_records + if record.get('type') in ['privilegeUpdate', 'licenseUpdate'] and record.get('updateType') == 'investigation' + ] + encumbrance_update_records = [ + record + for record in all_records + if record.get('type') in ['privilegeUpdate', 'licenseUpdate'] and record.get('updateType') == 'encumbrance' + ] + + if ( + not investigation_records + and not encumbrance_records + and not investigation_update_records + and not encumbrance_update_records + ): + logger.info('No investigation or encumbrance records found to clean up') + return + + # Delete each investigation and encumbrance record + dynamodb_table = get_provider_user_dynamodb_table() + + for record in investigation_records: + pk = record['pk'] + sk = record['sk'] + logger.info(f'Deleting investigation record: {pk} / {sk}') + dynamodb_table.delete_item(Key={'pk': pk, 'sk': sk}) + + for record in encumbrance_records: + pk = record['pk'] + sk = record['sk'] + logger.info(f'Deleting encumbrance record: {pk} / {sk}') + dynamodb_table.delete_item(Key={'pk': pk, 'sk': sk}) + + for record in investigation_update_records: + pk = record['pk'] + sk = record['sk'] + logger.info(f'Deleting investigation update record: {pk} / {sk}') + dynamodb_table.delete_item(Key={'pk': pk, 'sk': sk}) + + for record in encumbrance_update_records: + pk = record['pk'] + sk = record['sk'] + logger.info(f'Deleting encumbrance update record: {pk} / {sk}') + dynamodb_table.delete_item(Key={'pk': pk, 'sk': sk}) + + logger.info( + f'Cleaned up {len(investigation_records)} investigation records, ' + f'{len(encumbrance_records)} encumbrance records, ' + f'{len(investigation_update_records)} investigation update records, and ' + f'{len(encumbrance_update_records)} encumbrance update records' + ) + + +def setup_test_environment(): + """ + Set up the test environment by cleaning investigations. + """ + logger.info('Setting up test environment...') + + # Clean up any existing investigations + clean_investigation_records() + + logger.info('Test environment setup complete') + + +def _get_license_data_from_provider_response(provider_data: dict, jurisdiction: str, license_type: str): + """Get license data from provider response.""" + return next( + ( + lic + for lic in provider_data['licenses'] + if lic['jurisdiction'] == jurisdiction and lic['licenseType'] == license_type + ), + None, + ) + + +def _get_privilege_data_from_provider_response(provider_data: dict, jurisdiction: str, license_type: str): + """Get privilege data from provider response.""" + return next( + ( + priv + for priv in provider_data['privileges'] + if priv['jurisdiction'] == jurisdiction and priv['licenseType'] == license_type + ), + None, + ) + + +def _verify_no_investigation_exists(record_type: str, jurisdiction: str, license_type: str): + """ + Verify that no open investigation records exist in the database and no investigation status or objects on the + record. + + :param record_type: 'privilege' or 'license' + :param jurisdiction: The jurisdiction of the record + :param license_type: The license type of the record + """ + # Check database for open investigation records + all_records = get_all_provider_database_records() + existing_investigations = [ + record for record in all_records if record.get('type') == 'investigation' and record.get('closeDate') is None + ] + + if existing_investigations: + raise SmokeTestFailureException('Open investigation already exists before creation test') + + # Check API for investigation status + provider_data = call_provider_users_me_endpoint() + + if record_type == 'privilege': + record_data = _get_privilege_data_from_provider_response(provider_data, jurisdiction, license_type) + else: + record_data = _get_license_data_from_provider_response(provider_data, jurisdiction, license_type) + + if not record_data: + raise SmokeTestFailureException(f'{record_type.title()} not found before investigation creation') + + if record_data.get('investigationStatus') is not None: + raise SmokeTestFailureException( + f'Expected {record_type} to not have investigation status, ' + f'but got: {record_data.get("investigationStatus")}' + ) + + if record_data.get('investigations'): + raise SmokeTestFailureException('Investigation objects still exist in API response') + + +def _verify_investigation_exists(record_type: str, jurisdiction: str, license_type: str): + """ + Verify that an open investigation exists and the record has investigation status. + + :param record_type: 'privilege' or 'license' + :param jurisdiction: The jurisdiction of the record + :param license_type: The license type of the record + :return: The investigation ID + """ + # Check database for investigation records + all_records = get_all_provider_database_records() + investigation_records = [ + record + for record in all_records + if record.get('type') == 'investigation' + and record.get('investigationAgainst') == record_type + and record.get('jurisdiction') == jurisdiction + and record.get('licenseType') == license_type + and record.get('closeDate') is None + ] + + if not investigation_records: + raise SmokeTestFailureException(f'No open {record_type} investigation found to close') + + # Check API for investigation status + provider_data = call_provider_users_me_endpoint() + + if record_type == 'privilege': + record_data = _get_privilege_data_from_provider_response(provider_data, jurisdiction, license_type) + else: + record_data = _get_license_data_from_provider_response(provider_data, jurisdiction, license_type) + + if not record_data: + raise SmokeTestFailureException(f'{record_type.title()} not found before investigation closing') + + if record_data.get('investigationStatus') != 'underInvestigation': + raise SmokeTestFailureException( + f'Expected {record_type} to have investigation status "underInvestigation" before closing, ' + f'but got: {record_data.get("investigationStatus")}' + ) + + if not record_data.get('investigations'): + raise SmokeTestFailureException('Investigation object not found in API response before closing') + + return investigation_records[0]['investigationId'] + + +def test_create_privilege_investigation(auth_headers): + """Test creating a privilege investigation.""" + logger.info('Testing privilege investigation creation...') + + provider_data = call_provider_users_me_endpoint() + provider_id = provider_data['providerId'] + compact = provider_data['compact'] + jurisdiction = provider_data['privileges'][0]['jurisdiction'] + license_type = provider_data['privileges'][0]['licenseType'] + license_type_abbreviation = get_license_type_abbreviation(license_type) + + _verify_no_investigation_exists('privilege', jurisdiction, license_type) + + # Create investigation (empty body required) + response = requests.post( + f'{config.api_base_url}/v1/compacts/{compact}/providers/{provider_id}/privileges/jurisdiction/{jurisdiction}' + f'/licenseType/{license_type_abbreviation}/investigation', + json={}, + headers=auth_headers, + timeout=30, + ) + + if response.status_code != 200: + raise SmokeTestFailureException( + f'Failed to create privilege investigation: {response.status_code} - {response.text}' + ) + + logger.info('Privilege investigation created successfully') + + # Wait for the investigation to be processed and DynamoDB eventual consistency + time.sleep(5) + + _verify_investigation_exists('privilege', jurisdiction, license_type) + + +def test_create_license_investigation(auth_headers): + """Test creating a license investigation.""" + logger.info('Testing license investigation creation...') + + provider_data = call_provider_users_me_endpoint() + provider_id = provider_data['providerId'] + compact = provider_data['compact'] + jurisdiction = provider_data['licenseJurisdiction'] + license_type = provider_data['licenses'][0]['licenseType'] + license_type_abbreviation = get_license_type_abbreviation(license_type) + + _verify_no_investigation_exists('license', jurisdiction, license_type) + + # Create investigation (empty body required) + response = requests.post( + f'{config.api_base_url}/v1/compacts/{compact}/providers/{provider_id}/licenses/jurisdiction/{jurisdiction}' + f'/licenseType/{license_type_abbreviation}/investigation', + json={}, + headers=auth_headers, + timeout=30, + ) + + if response.status_code != 200: + raise SmokeTestFailureException( + f'Failed to create license investigation: {response.status_code} - {response.text}' + ) + + logger.info('License investigation created successfully') + + # Wait for the investigation to be processed and DynamoDB eventual consistency + time.sleep(5) + + _verify_investigation_exists('license', jurisdiction, license_type) + + +def test_close_privilege_investigation(auth_headers): + """Test closing a privilege investigation.""" + logger.info('Testing privilege investigation closing...') + + provider_data = call_provider_users_me_endpoint() + provider_id = provider_data['providerId'] + compact = provider_data['compact'] + jurisdiction = provider_data['privileges'][0]['jurisdiction'] + license_type = provider_data['privileges'][0]['licenseType'] + license_type_abbreviation = get_license_type_abbreviation(license_type) + + investigation_id = _verify_investigation_exists('privilege', jurisdiction, license_type) + + # Close investigation (no encumbrance) + response = requests.patch( + f'{config.api_base_url}/v1/compacts/{compact}/providers/{provider_id}/privileges/jurisdiction/{jurisdiction}' + f'/licenseType/{license_type_abbreviation}/investigation/{investigation_id}', + json={'action': 'close'}, + headers=auth_headers, + timeout=30, + ) + + if response.status_code != 200: + raise SmokeTestFailureException( + f'Failed to close privilege investigation: {response.status_code} - {response.text}' + ) + + logger.info('Privilege investigation closed successfully') + + # Wait for the investigation to be processed and DynamoDB eventual consistency + time.sleep(5) + + _verify_no_investigation_exists('privilege', jurisdiction, license_type) + + +def test_close_license_investigation(auth_headers): + """Test closing a license investigation.""" + logger.info('Testing license investigation closing...') + + provider_data = call_provider_users_me_endpoint() + provider_id = provider_data['providerId'] + compact = provider_data['compact'] + jurisdiction = provider_data['licenseJurisdiction'] + license_type = provider_data['licenses'][0]['licenseType'] + license_type_abbreviation = get_license_type_abbreviation(license_type) + + investigation_id = _verify_investigation_exists('license', jurisdiction, license_type) + + # Close investigation (no encumbrance) + response = requests.patch( + f'{config.api_base_url}/v1/compacts/{compact}/providers/{provider_id}/licenses/jurisdiction/{jurisdiction}' + f'/licenseType/{license_type_abbreviation}/investigation/{investigation_id}', + headers=auth_headers, + json={'action': 'close'}, + timeout=30, + ) + + if response.status_code != 200: + raise SmokeTestFailureException( + f'Failed to close license investigation: {response.status_code} - {response.text}' + ) + + logger.info('License investigation closed successfully') + + # Wait for the investigation to be processed and DynamoDB eventual consistency + time.sleep(5) + + _verify_no_investigation_exists('license', jurisdiction, license_type) + + +def test_close_privilege_investigation_with_encumbrance(auth_headers): + """Test closing a privilege investigation with encumbrance creation.""" + logger.info('Testing privilege investigation closing with encumbrance...') + + provider_data = call_provider_users_me_endpoint() + provider_id = provider_data['providerId'] + compact = provider_data['compact'] + jurisdiction = provider_data['privileges'][0]['jurisdiction'] + license_type = provider_data['privileges'][0]['licenseType'] + license_type_abbreviation = get_license_type_abbreviation(license_type) + + # Verify initial state: an open investigation should exist + investigation_id = _verify_investigation_exists('privilege', jurisdiction, license_type) + + # Verify privilege is not already encumbered (no adverse actions) + privilege_data = _get_privilege_data_from_provider_response(provider_data, jurisdiction, license_type) + if privilege_data.get('adverseActions'): + raise SmokeTestFailureException( + f'Expected privilege to not have adverse actions before closing with encumbrance, ' + f'but got: {privilege_data.get("adverseActions")}' + ) + + # Close investigation with encumbrance + close_data = { + 'action': 'close', + 'encumbrance': { + 'encumbranceEffectiveDate': '2024-01-15', + 'encumbranceType': 'fine', + 'clinicalPrivilegeActionCategory': 'Unsafe Practice or Substandard Care', + }, + } + + response = requests.patch( + f'{config.api_base_url}/v1/compacts/{compact}/providers/{provider_id}/privileges/jurisdiction/{jurisdiction}' + f'/licenseType/{license_type_abbreviation}/investigation/{investigation_id}', + json=close_data, + headers=auth_headers, + timeout=30, + ) + + if response.status_code != 200: + raise SmokeTestFailureException( + f'Failed to close privilege investigation with encumbrance: {response.status_code} - {response.text}' + ) + + logger.info('Privilege investigation closed with encumbrance successfully') + + # Wait for the investigation to be processed and DynamoDB eventual consistency + time.sleep(5) + + _verify_no_investigation_exists('privilege', jurisdiction, license_type) + # Verify encumbrance was created (adverse action exists) + provider_data = call_provider_users_me_endpoint() + privilege_data = _get_privilege_data_from_provider_response(provider_data, jurisdiction, license_type) + + if not privilege_data.get('adverseActions'): + raise SmokeTestFailureException( + f'Expected privilege to have adverse actions after closing with encumbrance, ' + f'but got: {privilege_data.get("adverseActions")}' + ) + + logger.info('Privilege investigation closing with encumbrance verified successfully') + + +def main(): + """Run all investigation smoke tests.""" + logger.info('Starting investigation smoke tests...') + + # Initialize variables for cleanup + staff_user_email = None + staff_user_sub = None + + try: + # Load test environment + load_smoke_test_env() + + # Set up test environment + setup_test_environment() + + # Create test staff user + staff_user_email = 'test-investigation-admin@example.com' + staff_user_sub = create_test_staff_user( + email=staff_user_email, + compact='aslp', + jurisdiction='ne', + permissions={'actions': {'admin'}, 'jurisdictions': {'ne': {'admin'}, 'co': {'admin'}, 'ky': {'admin'}}}, + ) + + # Get staff user auth headers once for reuse + auth_headers = get_staff_user_auth_headers(staff_user_email) + + # Run tests + setup_test_environment() + test_create_privilege_investigation(auth_headers) + test_close_privilege_investigation(auth_headers) + + # Test closing with encumbrance + setup_test_environment() + test_create_privilege_investigation(auth_headers) + test_close_privilege_investigation_with_encumbrance(auth_headers) + + # Test closing a license investigation + setup_test_environment() + test_create_license_investigation(auth_headers) + test_close_license_investigation(auth_headers) + + except Exception as e: + logger.error(f'Investigation smoke tests failed: {str(e)}') + raise + finally: + # Clean up test staff user + if staff_user_email and staff_user_sub: + delete_test_staff_user(staff_user_email, staff_user_sub, 'aslp') + clean_investigation_records() + + logger.info('All investigation smoke tests passed!') + + +if __name__ == '__main__': + main()