From 8c58b23e8f02c197ff468920ca315787a8d94d56 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Tue, 28 Jul 2026 10:10:50 -0500 Subject: [PATCH 01/14] Update encumbrance categories --- .../api-specification/latest-oas30.json | 84 ++++++++++++++----- .../internal/postman/postman-collection.json | 18 ++-- .../api-specification/latest-oas30.json | 24 ++++-- .../postman/postman-collection.json | 2 +- .../cc_common/data_model/schema/common.py | 12 ++- .../common/common_test/test_constants.py | 2 +- .../resources/api/adverse-action-post.json | 2 +- .../test_schema/test_adverse_action.py | 2 +- .../test_schema/test_investigation.py | 2 +- .../function/test_public_search_providers.py | 8 +- .../stacks/api_stack/v1_api/api_model.py | 12 ++- .../search_api_stack/v1_api/api_model.py | 12 ++- .../GET_PROVIDER_RESPONSE_SCHEMA.json | 36 ++++++-- .../LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json | 12 ++- ..._LICENSE_INVESTIGATION_REQUEST_SCHEMA.json | 12 ++- ...RIVILEGE_INVESTIGATION_REQUEST_SCHEMA.json | 12 ++- .../PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json | 12 ++- .../PROVIDER_USER_RESPONSE_SCHEMA.json | 24 ++++-- .../tests/smoke/encumbrance_smoke_tests.py | 10 +-- .../tests/smoke/investigation_smoke_tests.py | 2 +- 20 files changed, 217 insertions(+), 83 deletions(-) diff --git a/backend/social-work-app/docs/internal/api-specification/latest-oas30.json b/backend/social-work-app/docs/internal/api-specification/latest-oas30.json index 9b082782c2..5789979bd6 100644 --- a/backend/social-work-app/docs/internal/api-specification/latest-oas30.json +++ b/backend/social-work-app/docs/internal/api-specification/latest-oas30.json @@ -2561,9 +2561,15 @@ "items": { "type": "string", "enum": [ - "fraud", - "consumer harm", - "other" + "Non-Compliance With Requirements", + "Conflict of Interest", + "Substandard Care or Patient Neglect/Abuse", + "Criminal Conviction or Adjudication", + "Confidentiality, Consent or Disclosure Violations", + "Fraud, Deception, or Misrepresentation", + "Improper Supervision or Allowing Unlicensed Practice", + "Improper Prescribing, Dispensing, Administering Medication/Drug Violation", + "Other" ] } }, @@ -3350,9 +3356,15 @@ "items": { "type": "string", "enum": [ - "fraud", - "consumer harm", - "other" + "Non-Compliance With Requirements", + "Conflict of Interest", + "Substandard Care or Patient Neglect/Abuse", + "Criminal Conviction or Adjudication", + "Confidentiality, Consent or Disclosure Violations", + "Fraud, Deception, or Misrepresentation", + "Improper Supervision or Allowing Unlicensed Practice", + "Improper Prescribing, Dispensing, Administering Medication/Drug Violation", + "Other" ] } }, @@ -3509,9 +3521,15 @@ "items": { "type": "string", "enum": [ - "fraud", - "consumer harm", - "other" + "Non-Compliance With Requirements", + "Conflict of Interest", + "Substandard Care or Patient Neglect/Abuse", + "Criminal Conviction or Adjudication", + "Confidentiality, Consent or Disclosure Violations", + "Fraud, Deception, or Misrepresentation", + "Improper Supervision or Allowing Unlicensed Practice", + "Improper Prescribing, Dispensing, Administering Medication/Drug Violation", + "Other" ] } }, @@ -4063,9 +4081,15 @@ "items": { "type": "string", "enum": [ - "fraud", - "consumer harm", - "other" + "Non-Compliance With Requirements", + "Conflict of Interest", + "Substandard Care or Patient Neglect/Abuse", + "Criminal Conviction or Adjudication", + "Confidentiality, Consent or Disclosure Violations", + "Fraud, Deception, or Misrepresentation", + "Improper Supervision or Allowing Unlicensed Practice", + "Improper Prescribing, Dispensing, Administering Medication/Drug Violation", + "Other" ] } }, @@ -4182,9 +4206,15 @@ "items": { "type": "string", "enum": [ - "fraud", - "consumer harm", - "other" + "Non-Compliance With Requirements", + "Conflict of Interest", + "Substandard Care or Patient Neglect/Abuse", + "Criminal Conviction or Adjudication", + "Confidentiality, Consent or Disclosure Violations", + "Fraud, Deception, or Misrepresentation", + "Improper Supervision or Allowing Unlicensed Practice", + "Improper Prescribing, Dispensing, Administering Medication/Drug Violation", + "Other" ] } }, @@ -4321,9 +4351,15 @@ "items": { "type": "string", "enum": [ - "fraud", - "consumer harm", - "other" + "Non-Compliance With Requirements", + "Conflict of Interest", + "Substandard Care or Patient Neglect/Abuse", + "Criminal Conviction or Adjudication", + "Confidentiality, Consent or Disclosure Violations", + "Fraud, Deception, or Misrepresentation", + "Improper Supervision or Allowing Unlicensed Practice", + "Improper Prescribing, Dispensing, Administering Medication/Drug Violation", + "Other" ] } }, @@ -4369,9 +4405,15 @@ "items": { "type": "string", "enum": [ - "fraud", - "consumer harm", - "other" + "Non-Compliance With Requirements", + "Conflict of Interest", + "Substandard Care or Patient Neglect/Abuse", + "Criminal Conviction or Adjudication", + "Confidentiality, Consent or Disclosure Violations", + "Fraud, Deception, or Misrepresentation", + "Improper Supervision or Allowing Unlicensed Practice", + "Improper Prescribing, Dispensing, Administering Medication/Drug Violation", + "Other" ] } }, diff --git a/backend/social-work-app/docs/internal/postman/postman-collection.json b/backend/social-work-app/docs/internal/postman/postman-collection.json index dd107f4165..acd68a7bd6 100644 --- a/backend/social-work-app/docs/internal/postman/postman-collection.json +++ b/backend/social-work-app/docs/internal/postman/postman-collection.json @@ -1277,7 +1277,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"1160-06-23\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2718-12-08\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"al\",\n \"licenseScope\": \"single-state\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"00e7bb0f-4370-4dce-9d4b-6ab3154fc5f2\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"consumer harm\",\n \"other\"\n ],\n \"effectiveLiftDate\": \"1342-10-02\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"2156-10-30\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2461-12-19\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"va\",\n \"licenseScope\": \"multi-state\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"4a9cf136-1478-4d7d-920a-bf79858455ad\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"other\",\n \"other\"\n ],\n \"effectiveLiftDate\": \"1881-07-29\",\n \"liftingUser\": \"\"\n }\n ],\n \"birthMonthDay\": \"08-18\",\n \"compact\": \"socw\",\n \"dateOfExpiration\": \"1280-12-09\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"licenseJurisdiction\": \"ks\",\n \"licenses\": [\n {\n \"compact\": \"socw\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"2045-08-30\",\n \"dateOfIssuance\": \"2823-08-03\",\n \"dateOfRenewal\": \"1292-05-30\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"history\": [\n {\n \"compact\": \"socw\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"ks\",\n \"previous\": {\n \"dateOfExpiration\": \"2221-01-07\",\n \"dateOfIssuance\": \"1280-06-02\",\n \"dateOfRenewal\": \"1825-12-01\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"compactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1880-03-08\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+228872964\",\n \"licenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\",\n \"licenseScope\": \"single-state\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"renewal\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"licensed bachelors social worker\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2261-04-11\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"1652-11-05\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"1702-09-06\",\n \"phoneNumber\": \"+699958009619\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"1064-11-30\",\n \"licenseStatus\": \"inactive\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\",\n \"licenseScope\": \"multi-state\"\n }\n },\n {\n \"compact\": \"socw\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"tn\",\n \"previous\": {\n \"dateOfExpiration\": \"1297-10-30\",\n \"dateOfIssuance\": \"2426-05-08\",\n \"dateOfRenewal\": \"2524-05-03\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"2726-11-27\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+05907510297\",\n \"licenseStatus\": \"active\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\",\n \"licenseScope\": \"multi-state\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"lifting_encumbrance\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"licensed master social worker\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1349-08-17\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"1335-04-18\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"1014-01-31\",\n \"phoneNumber\": \"+828199787\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"2382-12-21\",\n \"licenseStatus\": \"inactive\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\",\n \"licenseScope\": \"single-state\"\n }\n }\n ],\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdiction\": \"wa\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"licenseScope\": \"single-state\",\n \"licenseStatus\": \"inactive\",\n \"licenseType\": \"licensed clinical social worker\",\n \"middleName\": \"\",\n \"providerId\": \"f60391ec-df56-438b-8874-358e7cd3fdf5\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"investigations\": [\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"ky\",\n \"licenseScope\": \"multi-state\",\n \"licenseType\": \"\",\n \"providerId\": \"1f8185a1-dd31-4bc1-be55-aa89287807b8\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"ks\",\n \"licenseScope\": \"single-state\",\n \"licenseType\": \"\",\n \"providerId\": \"bea3edae-8af3-4397-a5e6-d5326cac5206\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"licenseNumber\": \"\",\n \"investigationStatus\": \"underInvestigation\",\n \"dateOfBirth\": \"2589-10-13\",\n \"ssnLastFour\": \"6996\",\n \"phoneNumber\": \"+76733502344701\",\n \"licenseStatusName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"1922-07-21\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2512-11-04\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"ks\",\n \"licenseScope\": \"multi-state\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"35c884b5-ec90-485e-9864-3e75215f588c\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"consumer harm\",\n \"consumer harm\"\n ],\n \"effectiveLiftDate\": \"1170-11-31\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"2880-10-31\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1320-03-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"oh\",\n \"licenseScope\": \"multi-state\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"9ad53881-e267-44b2-b339-d50a6abef047\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"consumer harm\",\n \"fraud\"\n ],\n \"effectiveLiftDate\": \"1422-03-03\",\n \"liftingUser\": \"\"\n }\n ]\n },\n {\n \"compact\": \"socw\",\n \"compactEligibility\": \"eligible\",\n \"dateOfExpiration\": \"1250-09-04\",\n \"dateOfIssuance\": \"2658-08-01\",\n \"dateOfRenewal\": \"2869-10-30\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"history\": [\n {\n \"compact\": \"socw\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"md\",\n \"previous\": {\n \"dateOfExpiration\": \"1482-08-15\",\n \"dateOfIssuance\": \"2490-11-14\",\n \"dateOfRenewal\": \"1433-07-31\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"1303-10-05\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+0018548593\",\n \"licenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\",\n \"licenseScope\": \"single-state\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"renewal\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"licensed master social worker\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2352-12-31\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2225-10-01\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"1081-12-14\",\n \"phoneNumber\": \"+81818666589\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"2510-11-20\",\n \"licenseStatus\": \"active\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\",\n \"licenseScope\": \"single-state\"\n }\n },\n {\n \"compact\": \"socw\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"ks\",\n \"previous\": {\n \"dateOfExpiration\": \"2336-10-02\",\n \"dateOfIssuance\": \"1736-04-30\",\n \"dateOfRenewal\": \"1854-12-20\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"2950-07-01\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+8914586016122\",\n \"licenseStatus\": \"active\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\",\n \"licenseScope\": \"multi-state\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"renewal\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"licensed bachelors social worker\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"eligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2885-10-12\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"1258-12-18\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"1363-09-16\",\n \"phoneNumber\": \"+2051028595\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"1464-10-30\",\n \"licenseStatus\": \"inactive\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\",\n \"licenseScope\": \"single-state\"\n }\n }\n ],\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdiction\": \"co\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"licenseScope\": \"multi-state\",\n \"licenseStatus\": \"inactive\",\n \"licenseType\": \"licensed clinical social worker\",\n \"middleName\": \"\",\n \"providerId\": \"7e09156b-f8c2-4862-b7f3-5157b2f07f43\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"investigations\": [\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"wa\",\n \"licenseScope\": \"single-state\",\n \"licenseType\": \"\",\n \"providerId\": \"3320bcaa-9f82-4e08-8f9a-50dc28935980\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"wa\",\n \"licenseScope\": \"single-state\",\n \"licenseType\": \"\",\n \"providerId\": \"3699cad2-8687-4190-92a4-cb3f64a0d460\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"licenseNumber\": \"\",\n \"investigationStatus\": \"underInvestigation\",\n \"dateOfBirth\": \"2804-09-08\",\n \"ssnLastFour\": \"2627\",\n \"phoneNumber\": \"+164734901773\",\n \"licenseStatusName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"1138-12-01\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2164-03-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"tn\",\n \"licenseScope\": \"single-state\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"f5f102da-48d5-4b54-8cae-3f4d3a671fcf\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"consumer harm\",\n \"fraud\"\n ],\n \"effectiveLiftDate\": \"2205-04-01\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"2512-12-15\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2684-10-10\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"az\",\n \"licenseScope\": \"single-state\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"89fd3a63-b365-4d4b-8f12-dd87e5edc6ef\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"consumer harm\",\n \"other\"\n ],\n \"effectiveLiftDate\": \"2897-11-27\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"privileges\": [\n {\n \"administratorSetStatus\": \"active\",\n \"compact\": \"socw\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"2576-11-31\",\n \"history\": [\n {\n \"compact\": \"socw\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"md\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1718-08-14\",\n \"licenseJurisdiction\": \"tn\",\n \"compact\": \"socw\",\n \"providerId\": \"641b6c40-e394-4a04-8223-0a01311702a9\",\n \"jurisdiction\": \"az\",\n \"type\": \"privilege\",\n \"status\": \"active\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"renewal\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"licensed clinical social worker\",\n \"updatedValues\": {\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"2929-11-07\",\n \"licenseJurisdiction\": \"tn\",\n \"compact\": \"socw\",\n \"providerId\": \"8fb4643f-8c7d-4081-829c-6494a12be670\",\n \"jurisdiction\": \"co\",\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"status\": \"inactive\"\n }\n },\n {\n \"compact\": \"socw\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"al\",\n \"previous\": {\n \"administratorSetStatus\": \"active\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"2462-02-07\",\n \"licenseJurisdiction\": \"ks\",\n \"compact\": \"socw\",\n \"providerId\": \"db84b60a-eb5a-4837-a460-7bb61b89589b\",\n \"jurisdiction\": \"al\",\n \"type\": \"privilege\",\n \"status\": \"inactive\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"renewal\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"licensed master social worker\",\n \"updatedValues\": {\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"1175-01-31\",\n \"licenseJurisdiction\": \"az\",\n \"compact\": \"socw\",\n \"providerId\": \"5dc09001-1e59-4959-a599-8c1f0fec06e5\",\n \"jurisdiction\": \"tn\",\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"status\": \"active\"\n }\n }\n ],\n \"jurisdiction\": \"az\",\n \"licenseJurisdiction\": \"az\",\n \"licenseType\": \"licensed bachelors social worker\",\n \"providerId\": \"f329b4ce-e452-46a7-8934-8155538cd716\",\n \"status\": \"active\",\n \"type\": \"privilege\",\n \"investigationStatus\": \"underInvestigation\",\n \"investigations\": [\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"ks\",\n \"licenseScope\": \"single-state\",\n \"licenseType\": \"\",\n \"providerId\": \"c4d3fec4-5881-48ce-b19e-0b74edb8bbd4\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"oh\",\n \"licenseScope\": \"single-state\",\n \"licenseType\": \"\",\n \"providerId\": \"9cd326d9-3bd9-404c-a86f-7e2c0528306c\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"1463-01-05\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2747-10-16\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"va\",\n \"licenseScope\": \"single-state\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"d68dfee6-451a-4e1a-9a9d-f26af02e9b2f\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"fraud\",\n \"other\"\n ],\n \"effectiveLiftDate\": \"2386-10-05\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"2449-08-06\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2471-11-17\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"ks\",\n \"licenseScope\": \"single-state\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"9b830bdd-3b8d-42eb-b00b-d7165d32d537\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"other\",\n \"consumer harm\"\n ],\n \"effectiveLiftDate\": \"2820-09-03\",\n \"liftingUser\": \"\"\n }\n ]\n },\n {\n \"administratorSetStatus\": \"active\",\n \"compact\": \"socw\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"2640-03-22\",\n \"history\": [\n {\n \"compact\": \"socw\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"az\",\n \"previous\": {\n \"administratorSetStatus\": \"active\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1072-10-30\",\n \"licenseJurisdiction\": \"oh\",\n \"compact\": \"socw\",\n \"providerId\": \"dc7ee300-0359-4b89-9cae-f926fee0d44e\",\n \"jurisdiction\": \"oh\",\n \"type\": \"privilege\",\n \"status\": \"inactive\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"expiration\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"licensed bachelors social worker\",\n \"updatedValues\": {\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"1216-03-01\",\n \"licenseJurisdiction\": \"ks\",\n \"compact\": \"socw\",\n \"providerId\": \"291a80ea-0792-498c-91dc-ca05c99a5c42\",\n \"jurisdiction\": \"oh\",\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"status\": \"active\"\n }\n },\n {\n \"compact\": \"socw\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"ky\",\n \"previous\": {\n \"administratorSetStatus\": \"active\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"2251-08-05\",\n \"licenseJurisdiction\": \"wa\",\n \"compact\": \"socw\",\n \"providerId\": \"5842b9b4-06a7-49b5-9de8-ea0a610d1bba\",\n \"jurisdiction\": \"md\",\n \"type\": \"privilege\",\n \"status\": \"active\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"renewal\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"licensed master social worker\",\n \"updatedValues\": {\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"1510-08-31\",\n \"licenseJurisdiction\": \"co\",\n \"compact\": \"socw\",\n \"providerId\": \"83c3146f-6831-4c4d-ba4c-76cce2a8d777\",\n \"jurisdiction\": \"al\",\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"status\": \"inactive\"\n }\n }\n ],\n \"jurisdiction\": \"oh\",\n \"licenseJurisdiction\": \"ky\",\n \"licenseType\": \"licensed bachelors social worker\",\n \"providerId\": \"b93e01ad-7dcc-4c22-abcb-650166be90db\",\n \"status\": \"active\",\n \"type\": \"privilege\",\n \"investigationStatus\": \"underInvestigation\",\n \"investigations\": [\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"md\",\n \"licenseScope\": \"multi-state\",\n \"licenseType\": \"\",\n \"providerId\": \"e42f9896-f31a-43ea-a6b9-4eb835ba91ad\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"oh\",\n \"licenseScope\": \"single-state\",\n \"licenseType\": \"\",\n \"providerId\": \"5f20c301-3ab6-48c0-aea5-2f60f5940b7a\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"1627-10-12\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1337-10-04\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"tn\",\n \"licenseScope\": \"single-state\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"c283f6e9-bf3d-49a7-a64a-e175389cd173\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"consumer harm\",\n \"other\"\n ],\n \"effectiveLiftDate\": \"1675-12-05\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"2072-10-31\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1382-11-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"va\",\n \"licenseScope\": \"single-state\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"4fbfa1ab-7422-41ac-bc80-cf2ee6255dd5\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"fraud\",\n \"other\"\n ],\n \"effectiveLiftDate\": \"2776-04-20\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"providerId\": \"4487f21b-62b6-435b-ab99-27cdd19f5dd2\",\n \"type\": \"provider\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2328-12-30\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"suffix\": \"\",\n \"ssnLastFour\": \"8801\",\n \"licenseStatus\": \"inactive\",\n \"middleName\": \"\"\n}", + "body": "{\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"1160-06-23\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2718-12-08\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"al\",\n \"licenseScope\": \"single-state\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"00e7bb0f-4370-4dce-9d4b-6ab3154fc5f2\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"Substandard Care or Patient Neglect/Abuse\",\n \"Other\"\n ],\n \"effectiveLiftDate\": \"1342-10-02\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"2156-10-30\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2461-12-19\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"va\",\n \"licenseScope\": \"multi-state\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"4a9cf136-1478-4d7d-920a-bf79858455ad\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"Other\",\n \"Other\"\n ],\n \"effectiveLiftDate\": \"1881-07-29\",\n \"liftingUser\": \"\"\n }\n ],\n \"birthMonthDay\": \"08-18\",\n \"compact\": \"socw\",\n \"dateOfExpiration\": \"1280-12-09\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"licenseJurisdiction\": \"ks\",\n \"licenses\": [\n {\n \"compact\": \"socw\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"2045-08-30\",\n \"dateOfIssuance\": \"2823-08-03\",\n \"dateOfRenewal\": \"1292-05-30\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"history\": [\n {\n \"compact\": \"socw\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"ks\",\n \"previous\": {\n \"dateOfExpiration\": \"2221-01-07\",\n \"dateOfIssuance\": \"1280-06-02\",\n \"dateOfRenewal\": \"1825-12-01\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"compactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1880-03-08\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+228872964\",\n \"licenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\",\n \"licenseScope\": \"single-state\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"renewal\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"licensed bachelors social worker\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2261-04-11\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"1652-11-05\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"1702-09-06\",\n \"phoneNumber\": \"+699958009619\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"1064-11-30\",\n \"licenseStatus\": \"inactive\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\",\n \"licenseScope\": \"multi-state\"\n }\n },\n {\n \"compact\": \"socw\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"tn\",\n \"previous\": {\n \"dateOfExpiration\": \"1297-10-30\",\n \"dateOfIssuance\": \"2426-05-08\",\n \"dateOfRenewal\": \"2524-05-03\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"2726-11-27\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+05907510297\",\n \"licenseStatus\": \"active\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\",\n \"licenseScope\": \"multi-state\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"lifting_encumbrance\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"licensed master social worker\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1349-08-17\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"1335-04-18\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"1014-01-31\",\n \"phoneNumber\": \"+828199787\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"2382-12-21\",\n \"licenseStatus\": \"inactive\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\",\n \"licenseScope\": \"single-state\"\n }\n }\n ],\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdiction\": \"wa\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"licenseScope\": \"single-state\",\n \"licenseStatus\": \"inactive\",\n \"licenseType\": \"licensed clinical social worker\",\n \"middleName\": \"\",\n \"providerId\": \"f60391ec-df56-438b-8874-358e7cd3fdf5\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"investigations\": [\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"ky\",\n \"licenseScope\": \"multi-state\",\n \"licenseType\": \"\",\n \"providerId\": \"1f8185a1-dd31-4bc1-be55-aa89287807b8\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"ks\",\n \"licenseScope\": \"single-state\",\n \"licenseType\": \"\",\n \"providerId\": \"bea3edae-8af3-4397-a5e6-d5326cac5206\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"licenseNumber\": \"\",\n \"investigationStatus\": \"underInvestigation\",\n \"dateOfBirth\": \"2589-10-13\",\n \"ssnLastFour\": \"6996\",\n \"phoneNumber\": \"+76733502344701\",\n \"licenseStatusName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"1922-07-21\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2512-11-04\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"ks\",\n \"licenseScope\": \"multi-state\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"35c884b5-ec90-485e-9864-3e75215f588c\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"Substandard Care or Patient Neglect/Abuse\",\n \"Substandard Care or Patient Neglect/Abuse\"\n ],\n \"effectiveLiftDate\": \"1170-11-31\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"2880-10-31\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1320-03-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"oh\",\n \"licenseScope\": \"multi-state\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"9ad53881-e267-44b2-b339-d50a6abef047\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"Substandard Care or Patient Neglect/Abuse\",\n \"Fraud, Deception, or Misrepresentation\"\n ],\n \"effectiveLiftDate\": \"1422-03-03\",\n \"liftingUser\": \"\"\n }\n ]\n },\n {\n \"compact\": \"socw\",\n \"compactEligibility\": \"eligible\",\n \"dateOfExpiration\": \"1250-09-04\",\n \"dateOfIssuance\": \"2658-08-01\",\n \"dateOfRenewal\": \"2869-10-30\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"history\": [\n {\n \"compact\": \"socw\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"md\",\n \"previous\": {\n \"dateOfExpiration\": \"1482-08-15\",\n \"dateOfIssuance\": \"2490-11-14\",\n \"dateOfRenewal\": \"1433-07-31\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"1303-10-05\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+0018548593\",\n \"licenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\",\n \"licenseScope\": \"single-state\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"renewal\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"licensed master social worker\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2352-12-31\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2225-10-01\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"1081-12-14\",\n \"phoneNumber\": \"+81818666589\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"2510-11-20\",\n \"licenseStatus\": \"active\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\",\n \"licenseScope\": \"single-state\"\n }\n },\n {\n \"compact\": \"socw\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"ks\",\n \"previous\": {\n \"dateOfExpiration\": \"2336-10-02\",\n \"dateOfIssuance\": \"1736-04-30\",\n \"dateOfRenewal\": \"1854-12-20\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"2950-07-01\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+8914586016122\",\n \"licenseStatus\": \"active\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\",\n \"licenseScope\": \"multi-state\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"renewal\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"licensed bachelors social worker\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"eligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2885-10-12\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"1258-12-18\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"1363-09-16\",\n \"phoneNumber\": \"+2051028595\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"1464-10-30\",\n \"licenseStatus\": \"inactive\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\",\n \"licenseScope\": \"single-state\"\n }\n }\n ],\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdiction\": \"co\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"licenseScope\": \"multi-state\",\n \"licenseStatus\": \"inactive\",\n \"licenseType\": \"licensed clinical social worker\",\n \"middleName\": \"\",\n \"providerId\": \"7e09156b-f8c2-4862-b7f3-5157b2f07f43\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"investigations\": [\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"wa\",\n \"licenseScope\": \"single-state\",\n \"licenseType\": \"\",\n \"providerId\": \"3320bcaa-9f82-4e08-8f9a-50dc28935980\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"wa\",\n \"licenseScope\": \"single-state\",\n \"licenseType\": \"\",\n \"providerId\": \"3699cad2-8687-4190-92a4-cb3f64a0d460\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"licenseNumber\": \"\",\n \"investigationStatus\": \"underInvestigation\",\n \"dateOfBirth\": \"2804-09-08\",\n \"ssnLastFour\": \"2627\",\n \"phoneNumber\": \"+164734901773\",\n \"licenseStatusName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"1138-12-01\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2164-03-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"tn\",\n \"licenseScope\": \"single-state\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"f5f102da-48d5-4b54-8cae-3f4d3a671fcf\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"Substandard Care or Patient Neglect/Abuse\",\n \"Fraud, Deception, or Misrepresentation\"\n ],\n \"effectiveLiftDate\": \"2205-04-01\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"2512-12-15\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2684-10-10\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"az\",\n \"licenseScope\": \"single-state\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"89fd3a63-b365-4d4b-8f12-dd87e5edc6ef\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"Substandard Care or Patient Neglect/Abuse\",\n \"Other\"\n ],\n \"effectiveLiftDate\": \"2897-11-27\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"privileges\": [\n {\n \"administratorSetStatus\": \"active\",\n \"compact\": \"socw\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"2576-11-31\",\n \"history\": [\n {\n \"compact\": \"socw\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"md\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1718-08-14\",\n \"licenseJurisdiction\": \"tn\",\n \"compact\": \"socw\",\n \"providerId\": \"641b6c40-e394-4a04-8223-0a01311702a9\",\n \"jurisdiction\": \"az\",\n \"type\": \"privilege\",\n \"status\": \"active\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"renewal\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"licensed clinical social worker\",\n \"updatedValues\": {\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"2929-11-07\",\n \"licenseJurisdiction\": \"tn\",\n \"compact\": \"socw\",\n \"providerId\": \"8fb4643f-8c7d-4081-829c-6494a12be670\",\n \"jurisdiction\": \"co\",\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"status\": \"inactive\"\n }\n },\n {\n \"compact\": \"socw\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"al\",\n \"previous\": {\n \"administratorSetStatus\": \"active\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"2462-02-07\",\n \"licenseJurisdiction\": \"ks\",\n \"compact\": \"socw\",\n \"providerId\": \"db84b60a-eb5a-4837-a460-7bb61b89589b\",\n \"jurisdiction\": \"al\",\n \"type\": \"privilege\",\n \"status\": \"inactive\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"renewal\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"licensed master social worker\",\n \"updatedValues\": {\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"1175-01-31\",\n \"licenseJurisdiction\": \"az\",\n \"compact\": \"socw\",\n \"providerId\": \"5dc09001-1e59-4959-a599-8c1f0fec06e5\",\n \"jurisdiction\": \"tn\",\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"status\": \"active\"\n }\n }\n ],\n \"jurisdiction\": \"az\",\n \"licenseJurisdiction\": \"az\",\n \"licenseType\": \"licensed bachelors social worker\",\n \"providerId\": \"f329b4ce-e452-46a7-8934-8155538cd716\",\n \"status\": \"active\",\n \"type\": \"privilege\",\n \"investigationStatus\": \"underInvestigation\",\n \"investigations\": [\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"ks\",\n \"licenseScope\": \"single-state\",\n \"licenseType\": \"\",\n \"providerId\": \"c4d3fec4-5881-48ce-b19e-0b74edb8bbd4\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"oh\",\n \"licenseScope\": \"single-state\",\n \"licenseType\": \"\",\n \"providerId\": \"9cd326d9-3bd9-404c-a86f-7e2c0528306c\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"1463-01-05\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2747-10-16\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"va\",\n \"licenseScope\": \"single-state\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"d68dfee6-451a-4e1a-9a9d-f26af02e9b2f\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"Fraud, Deception, or Misrepresentation\",\n \"Other\"\n ],\n \"effectiveLiftDate\": \"2386-10-05\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"2449-08-06\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2471-11-17\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"ks\",\n \"licenseScope\": \"single-state\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"9b830bdd-3b8d-42eb-b00b-d7165d32d537\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"Other\",\n \"Substandard Care or Patient Neglect/Abuse\"\n ],\n \"effectiveLiftDate\": \"2820-09-03\",\n \"liftingUser\": \"\"\n }\n ]\n },\n {\n \"administratorSetStatus\": \"active\",\n \"compact\": \"socw\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"2640-03-22\",\n \"history\": [\n {\n \"compact\": \"socw\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"az\",\n \"previous\": {\n \"administratorSetStatus\": \"active\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1072-10-30\",\n \"licenseJurisdiction\": \"oh\",\n \"compact\": \"socw\",\n \"providerId\": \"dc7ee300-0359-4b89-9cae-f926fee0d44e\",\n \"jurisdiction\": \"oh\",\n \"type\": \"privilege\",\n \"status\": \"inactive\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"expiration\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"licensed bachelors social worker\",\n \"updatedValues\": {\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"1216-03-01\",\n \"licenseJurisdiction\": \"ks\",\n \"compact\": \"socw\",\n \"providerId\": \"291a80ea-0792-498c-91dc-ca05c99a5c42\",\n \"jurisdiction\": \"oh\",\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"status\": \"active\"\n }\n },\n {\n \"compact\": \"socw\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"ky\",\n \"previous\": {\n \"administratorSetStatus\": \"active\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"2251-08-05\",\n \"licenseJurisdiction\": \"wa\",\n \"compact\": \"socw\",\n \"providerId\": \"5842b9b4-06a7-49b5-9de8-ea0a610d1bba\",\n \"jurisdiction\": \"md\",\n \"type\": \"privilege\",\n \"status\": \"active\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"renewal\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"licensed master social worker\",\n \"updatedValues\": {\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"1510-08-31\",\n \"licenseJurisdiction\": \"co\",\n \"compact\": \"socw\",\n \"providerId\": \"83c3146f-6831-4c4d-ba4c-76cce2a8d777\",\n \"jurisdiction\": \"al\",\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"status\": \"inactive\"\n }\n }\n ],\n \"jurisdiction\": \"oh\",\n \"licenseJurisdiction\": \"ky\",\n \"licenseType\": \"licensed bachelors social worker\",\n \"providerId\": \"b93e01ad-7dcc-4c22-abcb-650166be90db\",\n \"status\": \"active\",\n \"type\": \"privilege\",\n \"investigationStatus\": \"underInvestigation\",\n \"investigations\": [\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"md\",\n \"licenseScope\": \"multi-state\",\n \"licenseType\": \"\",\n \"providerId\": \"e42f9896-f31a-43ea-a6b9-4eb835ba91ad\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"oh\",\n \"licenseScope\": \"single-state\",\n \"licenseType\": \"\",\n \"providerId\": \"5f20c301-3ab6-48c0-aea5-2f60f5940b7a\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"1627-10-12\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1337-10-04\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"tn\",\n \"licenseScope\": \"single-state\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"c283f6e9-bf3d-49a7-a64a-e175389cd173\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"Substandard Care or Patient Neglect/Abuse\",\n \"Other\"\n ],\n \"effectiveLiftDate\": \"1675-12-05\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"2072-10-31\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1382-11-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"va\",\n \"licenseScope\": \"single-state\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"4fbfa1ab-7422-41ac-bc80-cf2ee6255dd5\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"Fraud, Deception, or Misrepresentation\",\n \"Other\"\n ],\n \"effectiveLiftDate\": \"2776-04-20\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"providerId\": \"4487f21b-62b6-435b-ab99-27cdd19f5dd2\",\n \"type\": \"provider\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2328-12-30\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"suffix\": \"\",\n \"ssnLastFour\": \"8801\",\n \"licenseStatus\": \"inactive\",\n \"middleName\": \"\"\n}", "code": 200, "cookie": [], "header": [ @@ -1358,7 +1358,7 @@ "language": "json" } }, - "raw": "{\n \"clinicalPrivilegeActionCategories\": [\n \"consumer harm\",\n \"fraud\"\n ],\n \"encumbranceEffectiveDate\": \"2858-05-18\",\n \"encumbranceType\": \"revocation\",\n \"licenseScope\": \"multi-state\"\n}" + "raw": "{\n \"clinicalPrivilegeActionCategories\": [\n \"Substandard Care or Patient Neglect/Abuse\",\n \"Fraud, Deception, or Misrepresentation\"\n ],\n \"encumbranceEffectiveDate\": \"2858-05-18\",\n \"encumbranceType\": \"revocation\",\n \"licenseScope\": \"multi-state\"\n}" }, "description": {}, "header": [ @@ -1458,7 +1458,7 @@ "language": "json" } }, - "raw": "{\n \"clinicalPrivilegeActionCategories\": [\n \"consumer harm\",\n \"fraud\"\n ],\n \"encumbranceEffectiveDate\": \"2858-05-18\",\n \"encumbranceType\": \"revocation\",\n \"licenseScope\": \"multi-state\"\n}" + "raw": "{\n \"clinicalPrivilegeActionCategories\": [\n \"Substandard Care or Patient Neglect/Abuse\",\n \"Fraud, Deception, or Misrepresentation\"\n ],\n \"encumbranceEffectiveDate\": \"2858-05-18\",\n \"encumbranceType\": \"revocation\",\n \"licenseScope\": \"multi-state\"\n}" }, "header": [ { @@ -1871,7 +1871,7 @@ "language": "json" } }, - "raw": "{\n \"action\": \"close\",\n \"licenseScope\": \"multi-state\",\n \"encumbrance\": {\n \"clinicalPrivilegeActionCategories\": [\n \"consumer harm\",\n \"fraud\"\n ],\n \"encumbranceEffectiveDate\": \"1397-09-25\",\n \"encumbranceType\": \"surrender of license\",\n \"licenseScope\": \"multi-state\"\n }\n}" + "raw": "{\n \"action\": \"close\",\n \"licenseScope\": \"multi-state\",\n \"encumbrance\": {\n \"clinicalPrivilegeActionCategories\": [\n \"Substandard Care or Patient Neglect/Abuse\",\n \"Fraud, Deception, or Misrepresentation\"\n ],\n \"encumbranceEffectiveDate\": \"1397-09-25\",\n \"encumbranceType\": \"surrender of license\",\n \"licenseScope\": \"multi-state\"\n }\n}" }, "description": {}, "header": [ @@ -1982,7 +1982,7 @@ "language": "json" } }, - "raw": "{\n \"action\": \"close\",\n \"licenseScope\": \"multi-state\",\n \"encumbrance\": {\n \"clinicalPrivilegeActionCategories\": [\n \"consumer harm\",\n \"fraud\"\n ],\n \"encumbranceEffectiveDate\": \"1397-09-25\",\n \"encumbranceType\": \"surrender of license\",\n \"licenseScope\": \"multi-state\"\n }\n}" + "raw": "{\n \"action\": \"close\",\n \"licenseScope\": \"multi-state\",\n \"encumbrance\": {\n \"clinicalPrivilegeActionCategories\": [\n \"Substandard Care or Patient Neglect/Abuse\",\n \"Fraud, Deception, or Misrepresentation\"\n ],\n \"encumbranceEffectiveDate\": \"1397-09-25\",\n \"encumbranceType\": \"surrender of license\",\n \"licenseScope\": \"multi-state\"\n }\n}" }, "header": [ { @@ -2084,7 +2084,7 @@ "language": "json" } }, - "raw": "{\n \"clinicalPrivilegeActionCategories\": [\n \"fraud\",\n \"consumer harm\"\n ],\n \"encumbranceEffectiveDate\": \"2816-09-30\",\n \"encumbranceType\": \"suspension\"\n}" + "raw": "{\n \"clinicalPrivilegeActionCategories\": [\n \"Fraud, Deception, or Misrepresentation\",\n \"Substandard Care or Patient Neglect/Abuse\"\n ],\n \"encumbranceEffectiveDate\": \"2816-09-30\",\n \"encumbranceType\": \"suspension\"\n}" }, "description": {}, "header": [ @@ -2184,7 +2184,7 @@ "language": "json" } }, - "raw": "{\n \"clinicalPrivilegeActionCategories\": [\n \"fraud\",\n \"consumer harm\"\n ],\n \"encumbranceEffectiveDate\": \"2816-09-30\",\n \"encumbranceType\": \"suspension\"\n}" + "raw": "{\n \"clinicalPrivilegeActionCategories\": [\n \"Fraud, Deception, or Misrepresentation\",\n \"Substandard Care or Patient Neglect/Abuse\"\n ],\n \"encumbranceEffectiveDate\": \"2816-09-30\",\n \"encumbranceType\": \"suspension\"\n}" }, "header": [ { @@ -2597,7 +2597,7 @@ "language": "json" } }, - "raw": "{\n \"action\": \"close\",\n \"encumbrance\": {\n \"clinicalPrivilegeActionCategories\": [\n \"fraud\",\n \"other\"\n ],\n \"encumbranceEffectiveDate\": \"2234-12-18\",\n \"encumbranceType\": \"surrender of license\"\n }\n}" + "raw": "{\n \"action\": \"close\",\n \"encumbrance\": {\n \"clinicalPrivilegeActionCategories\": [\n \"Fraud, Deception, or Misrepresentation\",\n \"Other\"\n ],\n \"encumbranceEffectiveDate\": \"2234-12-18\",\n \"encumbranceType\": \"surrender of license\"\n }\n}" }, "description": {}, "header": [ @@ -2708,7 +2708,7 @@ "language": "json" } }, - "raw": "{\n \"action\": \"close\",\n \"encumbrance\": {\n \"clinicalPrivilegeActionCategories\": [\n \"fraud\",\n \"other\"\n ],\n \"encumbranceEffectiveDate\": \"2234-12-18\",\n \"encumbranceType\": \"surrender of license\"\n }\n}" + "raw": "{\n \"action\": \"close\",\n \"encumbrance\": {\n \"clinicalPrivilegeActionCategories\": [\n \"Fraud, Deception, or Misrepresentation\",\n \"Other\"\n ],\n \"encumbranceEffectiveDate\": \"2234-12-18\",\n \"encumbranceType\": \"surrender of license\"\n }\n}" }, "header": [ { diff --git a/backend/social-work-app/docs/search-internal/api-specification/latest-oas30.json b/backend/social-work-app/docs/search-internal/api-specification/latest-oas30.json index 442b302c51..52aeb0f8e2 100644 --- a/backend/social-work-app/docs/search-internal/api-specification/latest-oas30.json +++ b/backend/social-work-app/docs/search-internal/api-specification/latest-oas30.json @@ -327,9 +327,15 @@ "items": { "type": "string", "enum": [ - "fraud", - "consumer harm", - "other" + "Non-Compliance With Requirements", + "Conflict of Interest", + "Substandard Care or Patient Neglect/Abuse", + "Criminal Conviction or Adjudication", + "Confidentiality, Consent or Disclosure Violations", + "Fraud, Deception, or Misrepresentation", + "Improper Supervision or Allowing Unlicensed Practice", + "Improper Prescribing, Dispensing, Administering Medication/Drug Violation", + "Other" ] } }, @@ -752,9 +758,15 @@ "items": { "type": "string", "enum": [ - "fraud", - "consumer harm", - "other" + "Non-Compliance With Requirements", + "Conflict of Interest", + "Substandard Care or Patient Neglect/Abuse", + "Criminal Conviction or Adjudication", + "Confidentiality, Consent or Disclosure Violations", + "Fraud, Deception, or Misrepresentation", + "Improper Supervision or Allowing Unlicensed Practice", + "Improper Prescribing, Dispensing, Administering Medication/Drug Violation", + "Other" ] } }, diff --git a/backend/social-work-app/docs/search-internal/postman/postman-collection.json b/backend/social-work-app/docs/search-internal/postman/postman-collection.json index fe26182830..ea33ad6a1e 100644 --- a/backend/social-work-app/docs/search-internal/postman/postman-collection.json +++ b/backend/social-work-app/docs/search-internal/postman/postman-collection.json @@ -465,7 +465,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"providers\": [\n {\n \"birthMonthDay\": \"02-38\",\n \"compact\": \"socw\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"licenseJurisdiction\": \"va\",\n \"licenseStatus\": \"inactive\",\n \"providerId\": \"74f5dae3-18fa-4a77-97a3-4b964921cdee\",\n \"type\": \"provider\",\n \"privileges\": [\n {\n \"administratorSetStatus\": \"active\",\n \"compact\": \"socw\",\n \"dateOfExpiration\": \"2263-06-30\",\n \"jurisdiction\": \"co\",\n \"licenseJurisdiction\": \"va\",\n \"licenseType\": \"\",\n \"providerId\": \"5f72bd71-b6d7-4d92-a07d-254810385b33\",\n \"status\": \"active\",\n \"type\": \"privilege\",\n \"investigationStatus\": \"underInvestigation\",\n \"investigations\": [\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"md\",\n \"licenseType\": \"\",\n \"providerId\": \"8855ac21-c48d-4155-985c-8ebb7f063c21\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"tn\",\n \"licenseType\": \"\",\n \"providerId\": \"bd793ab2-cec0-4878-aa55-593a7acb15a6\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"license\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"2846-02-09\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1621-12-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"oh\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"16afef54-434b-45ca-8f5c-4b1cfd813d81\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"other\",\n \"fraud\"\n ],\n \"effectiveLiftDate\": \"1155-05-30\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"privilege\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"2168-11-31\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2400-10-15\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"tn\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"ac01e6a8-4a49-4bb5-94de-354e61119576\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"other\",\n \"consumer harm\"\n ],\n \"effectiveLiftDate\": \"1492-08-30\",\n \"liftingUser\": \"\"\n }\n ]\n },\n {\n \"administratorSetStatus\": \"active\",\n \"compact\": \"socw\",\n \"dateOfExpiration\": \"1818-02-07\",\n \"jurisdiction\": \"wa\",\n \"licenseJurisdiction\": \"al\",\n \"licenseType\": \"\",\n \"providerId\": \"aeb5173a-78c6-44aa-810f-0d3c6950aa02\",\n \"status\": \"active\",\n \"type\": \"privilege\",\n \"investigationStatus\": \"underInvestigation\",\n \"investigations\": [\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"ks\",\n \"licenseType\": \"\",\n \"providerId\": \"081adac7-8778-41f0-9f7f-dd323cabcda8\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"va\",\n \"licenseType\": \"\",\n \"providerId\": \"7e1f8f70-feb6-4b9c-8386-be635eef91d6\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"license\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"2313-05-12\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1113-03-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"ks\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"ae8e065a-2d50-4b0a-b01c-316445fc9640\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"fraud\",\n \"other\"\n ],\n \"effectiveLiftDate\": \"2940-08-10\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"license\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"1727-10-30\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2088-06-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"md\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"3bd743ea-8677-48d2-a353-5c0c25fb3e41\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"other\",\n \"consumer harm\"\n ],\n \"effectiveLiftDate\": \"2500-11-31\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"suffix\": \"\",\n \"currentHomeJurisdiction\": \"va\",\n \"licenses\": [\n {\n \"compact\": \"socw\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"1895-10-21\",\n \"dateOfIssuance\": \"2600-12-30\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdiction\": \"md\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"licenseNumber\": \"\",\n \"licenseScope\": \"multi-state\",\n \"licenseStatus\": \"inactive\",\n \"licenseType\": \"\",\n \"providerId\": \"d07ede9f-f6da-4f87-b03a-fd5e228b177a\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"investigations\": [\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"ky\",\n \"licenseType\": \"\",\n \"providerId\": \"ee7fd305-d245-4b80-abbc-4e22aac3af17\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"tn\",\n \"licenseType\": \"\",\n \"providerId\": \"18b0e870-4c7c-4c82-8dd2-67ae396dd9af\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"dateOfRenewal\": \"1486-10-30\",\n \"investigationStatus\": \"underInvestigation\",\n \"phoneNumber\": \"+10037723998\",\n \"licenseStatusName\": \"\",\n \"middleName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"license\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"2285-07-16\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1815-02-31\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"oh\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"8aac86d2-017f-4a1d-b3b8-2743ee3ecda5\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"consumer harm\",\n \"other\"\n ],\n \"effectiveLiftDate\": \"2987-12-29\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"privilege\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"1146-09-18\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2087-12-23\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"al\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"cc107b7d-340d-431e-84af-fd73023914c0\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"fraud\",\n \"consumer harm\"\n ],\n \"effectiveLiftDate\": \"2904-12-14\",\n \"liftingUser\": \"\"\n }\n ]\n },\n {\n \"compact\": \"socw\",\n \"compactEligibility\": \"eligible\",\n \"dateOfExpiration\": \"1872-11-13\",\n \"dateOfIssuance\": \"2817-01-14\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdiction\": \"va\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseScope\": \"multi-state\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"\",\n \"providerId\": \"f5f8104a-ad41-49f9-91b8-2386212782ed\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"investigations\": [\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"md\",\n \"licenseType\": \"\",\n \"providerId\": \"c86f7639-53ed-437d-b3dc-75c0b6514931\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"tn\",\n \"licenseType\": \"\",\n \"providerId\": \"291000d5-d280-4115-bfad-28b277f00f6a\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"dateOfRenewal\": \"1536-08-05\",\n \"investigationStatus\": \"underInvestigation\",\n \"phoneNumber\": \"+30022521297176\",\n \"licenseStatusName\": \"\",\n \"middleName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"privilege\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"2277-10-03\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2509-12-08\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"co\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"bed70d59-e1c6-424b-b562-77299b5ab067\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"other\",\n \"fraud\"\n ],\n \"effectiveLiftDate\": \"1874-08-06\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"privilege\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"2601-10-08\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2912-04-02\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"ky\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"1ffe8f05-3c2f-489d-b388-b87538edb32b\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"fraud\",\n \"other\"\n ],\n \"effectiveLiftDate\": \"2363-10-31\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"middleName\": \"\",\n \"compactConnectRegisteredEmailAddress\": \"\"\n },\n {\n \"birthMonthDay\": \"12-13\",\n \"compact\": \"socw\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"licenseJurisdiction\": \"co\",\n \"licenseStatus\": \"inactive\",\n \"providerId\": \"9f4f898d-5c9a-4357-b2c8-d9f2318eb47e\",\n \"type\": \"provider\",\n \"privileges\": [\n {\n \"administratorSetStatus\": \"active\",\n \"compact\": \"socw\",\n \"dateOfExpiration\": \"1542-05-15\",\n \"jurisdiction\": \"az\",\n \"licenseJurisdiction\": \"wa\",\n \"licenseType\": \"\",\n \"providerId\": \"ffad1705-e9af-4790-879f-c713e0b33c9c\",\n \"status\": \"active\",\n \"type\": \"privilege\",\n \"investigationStatus\": \"underInvestigation\",\n \"investigations\": [\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"al\",\n \"licenseType\": \"\",\n \"providerId\": \"fa4e32c6-a68e-4eba-b461-e1bfab80db86\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"wa\",\n \"licenseType\": \"\",\n \"providerId\": \"97ba6371-9868-40c8-a1c8-33bea508d9f5\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"license\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"1216-03-30\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2562-10-03\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"az\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"8c0b5003-fbca-4328-9e9b-a09b84b64feb\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"fraud\",\n \"fraud\"\n ],\n \"effectiveLiftDate\": \"2482-07-08\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"license\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"1798-11-02\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2177-02-31\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"va\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"fa9b8be3-329f-42cb-a829-9b4645e53ff9\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"other\",\n \"other\"\n ],\n \"effectiveLiftDate\": \"2178-12-05\",\n \"liftingUser\": \"\"\n }\n ]\n },\n {\n \"administratorSetStatus\": \"active\",\n \"compact\": \"socw\",\n \"dateOfExpiration\": \"2916-01-27\",\n \"jurisdiction\": \"az\",\n \"licenseJurisdiction\": \"tn\",\n \"licenseType\": \"\",\n \"providerId\": \"81d10e2f-4b60-4095-8b37-0965ede8e91e\",\n \"status\": \"active\",\n \"type\": \"privilege\",\n \"investigationStatus\": \"underInvestigation\",\n \"investigations\": [\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"ks\",\n \"licenseType\": \"\",\n \"providerId\": \"034036df-6682-458d-b33d-1c3a33528a24\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"al\",\n \"licenseType\": \"\",\n \"providerId\": \"d6560741-e20a-4a5a-a68d-f7ee04c4b36c\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"privilege\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"2880-11-05\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2947-12-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"az\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"6115af67-4609-4a0b-a671-da74f8e5a896\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"fraud\",\n \"fraud\"\n ],\n \"effectiveLiftDate\": \"2906-11-30\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"privilege\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"1237-11-30\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1431-07-03\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"md\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"b547e42b-0f70-40e3-b35b-0d262fcc88ee\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"consumer harm\",\n \"other\"\n ],\n \"effectiveLiftDate\": \"2819-04-03\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"suffix\": \"\",\n \"currentHomeJurisdiction\": \"co\",\n \"licenses\": [\n {\n \"compact\": \"socw\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"2926-04-06\",\n \"dateOfIssuance\": \"1804-05-09\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdiction\": \"al\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"licenseNumber\": \"\",\n \"licenseScope\": \"single-state\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"\",\n \"providerId\": \"1d00d771-1736-44e5-8698-021e77ef9d7d\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"investigations\": [\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"wa\",\n \"licenseType\": \"\",\n \"providerId\": \"e2961462-b167-4d97-8e67-d9c3683a0587\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"co\",\n \"licenseType\": \"\",\n \"providerId\": \"70e95156-e697-470a-95b5-7ed0d8e5569f\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"dateOfRenewal\": \"1001-12-06\",\n \"investigationStatus\": \"underInvestigation\",\n \"phoneNumber\": \"+91177413084660\",\n \"licenseStatusName\": \"\",\n \"middleName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"privilege\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"2009-11-31\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2910-01-11\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"az\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"a7c92c4f-fcef-440a-b173-f2baf701b6af\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"other\",\n \"other\"\n ],\n \"effectiveLiftDate\": \"1892-10-05\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"license\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"1584-12-01\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1692-09-11\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"wa\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"9b0e2f4c-7c3e-44f7-b69c-da794983beca\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"consumer harm\",\n \"fraud\"\n ],\n \"effectiveLiftDate\": \"1779-12-21\",\n \"liftingUser\": \"\"\n }\n ]\n },\n {\n \"compact\": \"socw\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"2614-02-18\",\n \"dateOfIssuance\": \"1176-09-31\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdiction\": \"co\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseScope\": \"single-state\",\n \"licenseStatus\": \"inactive\",\n \"licenseType\": \"\",\n \"providerId\": \"43da7025-d2f0-4a33-a3ac-63d9822cefcd\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"investigations\": [\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"wa\",\n \"licenseType\": \"\",\n \"providerId\": \"056df11e-e52c-4d75-b3cc-cad9052b2344\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"wa\",\n \"licenseType\": \"\",\n \"providerId\": \"09dc62b4-9d60-4f31-8fee-5f4ce2c6a0be\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"dateOfRenewal\": \"2472-05-30\",\n \"investigationStatus\": \"underInvestigation\",\n \"phoneNumber\": \"+72464522486\",\n \"licenseStatusName\": \"\",\n \"middleName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"privilege\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"2887-01-07\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1605-05-04\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"va\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"cea13954-176f-4b73-8a3b-ad475eeb57df\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"consumer harm\",\n \"fraud\"\n ],\n \"effectiveLiftDate\": \"1827-05-22\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"privilege\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"1532-04-31\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1550-08-26\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"wa\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"117f1560-f5ab-42fb-914e-8f4573febac8\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"consumer harm\",\n \"other\"\n ],\n \"effectiveLiftDate\": \"1642-10-27\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"middleName\": \"\",\n \"compactConnectRegisteredEmailAddress\": \"\"\n }\n ],\n \"total\": {\n \"value\": \"\",\n \"relation\": \"gte\"\n },\n \"lastSort\": \"\"\n}", + "body": "{\n \"providers\": [\n {\n \"birthMonthDay\": \"02-38\",\n \"compact\": \"socw\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"licenseJurisdiction\": \"va\",\n \"licenseStatus\": \"inactive\",\n \"providerId\": \"74f5dae3-18fa-4a77-97a3-4b964921cdee\",\n \"type\": \"provider\",\n \"privileges\": [\n {\n \"administratorSetStatus\": \"active\",\n \"compact\": \"socw\",\n \"dateOfExpiration\": \"2263-06-30\",\n \"jurisdiction\": \"co\",\n \"licenseJurisdiction\": \"va\",\n \"licenseType\": \"\",\n \"providerId\": \"5f72bd71-b6d7-4d92-a07d-254810385b33\",\n \"status\": \"active\",\n \"type\": \"privilege\",\n \"investigationStatus\": \"underInvestigation\",\n \"investigations\": [\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"md\",\n \"licenseType\": \"\",\n \"providerId\": \"8855ac21-c48d-4155-985c-8ebb7f063c21\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"tn\",\n \"licenseType\": \"\",\n \"providerId\": \"bd793ab2-cec0-4878-aa55-593a7acb15a6\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"license\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"2846-02-09\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1621-12-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"oh\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"16afef54-434b-45ca-8f5c-4b1cfd813d81\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"Other\",\n \"Fraud, Deception, or Misrepresentation\"\n ],\n \"effectiveLiftDate\": \"1155-05-30\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"privilege\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"2168-11-31\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2400-10-15\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"tn\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"ac01e6a8-4a49-4bb5-94de-354e61119576\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"Other\",\n \"Substandard Care or Patient Neglect/Abuse\"\n ],\n \"effectiveLiftDate\": \"1492-08-30\",\n \"liftingUser\": \"\"\n }\n ]\n },\n {\n \"administratorSetStatus\": \"active\",\n \"compact\": \"socw\",\n \"dateOfExpiration\": \"1818-02-07\",\n \"jurisdiction\": \"wa\",\n \"licenseJurisdiction\": \"al\",\n \"licenseType\": \"\",\n \"providerId\": \"aeb5173a-78c6-44aa-810f-0d3c6950aa02\",\n \"status\": \"active\",\n \"type\": \"privilege\",\n \"investigationStatus\": \"underInvestigation\",\n \"investigations\": [\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"ks\",\n \"licenseType\": \"\",\n \"providerId\": \"081adac7-8778-41f0-9f7f-dd323cabcda8\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"va\",\n \"licenseType\": \"\",\n \"providerId\": \"7e1f8f70-feb6-4b9c-8386-be635eef91d6\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"license\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"2313-05-12\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1113-03-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"ks\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"ae8e065a-2d50-4b0a-b01c-316445fc9640\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"Fraud, Deception, or Misrepresentation\",\n \"Other\"\n ],\n \"effectiveLiftDate\": \"2940-08-10\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"license\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"1727-10-30\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2088-06-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"md\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"3bd743ea-8677-48d2-a353-5c0c25fb3e41\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"Other\",\n \"Substandard Care or Patient Neglect/Abuse\"\n ],\n \"effectiveLiftDate\": \"2500-11-31\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"suffix\": \"\",\n \"currentHomeJurisdiction\": \"va\",\n \"licenses\": [\n {\n \"compact\": \"socw\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"1895-10-21\",\n \"dateOfIssuance\": \"2600-12-30\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdiction\": \"md\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"licenseNumber\": \"\",\n \"licenseScope\": \"multi-state\",\n \"licenseStatus\": \"inactive\",\n \"licenseType\": \"\",\n \"providerId\": \"d07ede9f-f6da-4f87-b03a-fd5e228b177a\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"investigations\": [\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"ky\",\n \"licenseType\": \"\",\n \"providerId\": \"ee7fd305-d245-4b80-abbc-4e22aac3af17\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"tn\",\n \"licenseType\": \"\",\n \"providerId\": \"18b0e870-4c7c-4c82-8dd2-67ae396dd9af\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"dateOfRenewal\": \"1486-10-30\",\n \"investigationStatus\": \"underInvestigation\",\n \"phoneNumber\": \"+10037723998\",\n \"licenseStatusName\": \"\",\n \"middleName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"license\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"2285-07-16\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1815-02-31\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"oh\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"8aac86d2-017f-4a1d-b3b8-2743ee3ecda5\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"Substandard Care or Patient Neglect/Abuse\",\n \"Other\"\n ],\n \"effectiveLiftDate\": \"2987-12-29\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"privilege\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"1146-09-18\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2087-12-23\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"al\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"cc107b7d-340d-431e-84af-fd73023914c0\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"Fraud, Deception, or Misrepresentation\",\n \"Substandard Care or Patient Neglect/Abuse\"\n ],\n \"effectiveLiftDate\": \"2904-12-14\",\n \"liftingUser\": \"\"\n }\n ]\n },\n {\n \"compact\": \"socw\",\n \"compactEligibility\": \"eligible\",\n \"dateOfExpiration\": \"1872-11-13\",\n \"dateOfIssuance\": \"2817-01-14\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdiction\": \"va\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseScope\": \"multi-state\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"\",\n \"providerId\": \"f5f8104a-ad41-49f9-91b8-2386212782ed\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"investigations\": [\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"md\",\n \"licenseType\": \"\",\n \"providerId\": \"c86f7639-53ed-437d-b3dc-75c0b6514931\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"tn\",\n \"licenseType\": \"\",\n \"providerId\": \"291000d5-d280-4115-bfad-28b277f00f6a\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"dateOfRenewal\": \"1536-08-05\",\n \"investigationStatus\": \"underInvestigation\",\n \"phoneNumber\": \"+30022521297176\",\n \"licenseStatusName\": \"\",\n \"middleName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"privilege\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"2277-10-03\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2509-12-08\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"co\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"bed70d59-e1c6-424b-b562-77299b5ab067\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"Other\",\n \"Fraud, Deception, or Misrepresentation\"\n ],\n \"effectiveLiftDate\": \"1874-08-06\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"privilege\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"2601-10-08\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2912-04-02\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"ky\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"1ffe8f05-3c2f-489d-b388-b87538edb32b\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"Fraud, Deception, or Misrepresentation\",\n \"Other\"\n ],\n \"effectiveLiftDate\": \"2363-10-31\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"middleName\": \"\",\n \"compactConnectRegisteredEmailAddress\": \"\"\n },\n {\n \"birthMonthDay\": \"12-13\",\n \"compact\": \"socw\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"licenseJurisdiction\": \"co\",\n \"licenseStatus\": \"inactive\",\n \"providerId\": \"9f4f898d-5c9a-4357-b2c8-d9f2318eb47e\",\n \"type\": \"provider\",\n \"privileges\": [\n {\n \"administratorSetStatus\": \"active\",\n \"compact\": \"socw\",\n \"dateOfExpiration\": \"1542-05-15\",\n \"jurisdiction\": \"az\",\n \"licenseJurisdiction\": \"wa\",\n \"licenseType\": \"\",\n \"providerId\": \"ffad1705-e9af-4790-879f-c713e0b33c9c\",\n \"status\": \"active\",\n \"type\": \"privilege\",\n \"investigationStatus\": \"underInvestigation\",\n \"investigations\": [\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"al\",\n \"licenseType\": \"\",\n \"providerId\": \"fa4e32c6-a68e-4eba-b461-e1bfab80db86\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"wa\",\n \"licenseType\": \"\",\n \"providerId\": \"97ba6371-9868-40c8-a1c8-33bea508d9f5\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"license\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"1216-03-30\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2562-10-03\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"az\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"8c0b5003-fbca-4328-9e9b-a09b84b64feb\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"Fraud, Deception, or Misrepresentation\",\n \"Fraud, Deception, or Misrepresentation\"\n ],\n \"effectiveLiftDate\": \"2482-07-08\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"license\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"1798-11-02\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2177-02-31\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"va\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"fa9b8be3-329f-42cb-a829-9b4645e53ff9\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"Other\",\n \"Other\"\n ],\n \"effectiveLiftDate\": \"2178-12-05\",\n \"liftingUser\": \"\"\n }\n ]\n },\n {\n \"administratorSetStatus\": \"active\",\n \"compact\": \"socw\",\n \"dateOfExpiration\": \"2916-01-27\",\n \"jurisdiction\": \"az\",\n \"licenseJurisdiction\": \"tn\",\n \"licenseType\": \"\",\n \"providerId\": \"81d10e2f-4b60-4095-8b37-0965ede8e91e\",\n \"status\": \"active\",\n \"type\": \"privilege\",\n \"investigationStatus\": \"underInvestigation\",\n \"investigations\": [\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"ks\",\n \"licenseType\": \"\",\n \"providerId\": \"034036df-6682-458d-b33d-1c3a33528a24\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"al\",\n \"licenseType\": \"\",\n \"providerId\": \"d6560741-e20a-4a5a-a68d-f7ee04c4b36c\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"privilege\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"2880-11-05\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2947-12-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"az\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"6115af67-4609-4a0b-a671-da74f8e5a896\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"Fraud, Deception, or Misrepresentation\",\n \"Fraud, Deception, or Misrepresentation\"\n ],\n \"effectiveLiftDate\": \"2906-11-30\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"privilege\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"1237-11-30\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1431-07-03\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"md\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"b547e42b-0f70-40e3-b35b-0d262fcc88ee\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"Substandard Care or Patient Neglect/Abuse\",\n \"Other\"\n ],\n \"effectiveLiftDate\": \"2819-04-03\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"suffix\": \"\",\n \"currentHomeJurisdiction\": \"co\",\n \"licenses\": [\n {\n \"compact\": \"socw\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"2926-04-06\",\n \"dateOfIssuance\": \"1804-05-09\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdiction\": \"al\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"licenseNumber\": \"\",\n \"licenseScope\": \"single-state\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"\",\n \"providerId\": \"1d00d771-1736-44e5-8698-021e77ef9d7d\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"investigations\": [\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"wa\",\n \"licenseType\": \"\",\n \"providerId\": \"e2961462-b167-4d97-8e67-d9c3683a0587\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"co\",\n \"licenseType\": \"\",\n \"providerId\": \"70e95156-e697-470a-95b5-7ed0d8e5569f\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"dateOfRenewal\": \"1001-12-06\",\n \"investigationStatus\": \"underInvestigation\",\n \"phoneNumber\": \"+91177413084660\",\n \"licenseStatusName\": \"\",\n \"middleName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"privilege\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"2009-11-31\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2910-01-11\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"az\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"a7c92c4f-fcef-440a-b173-f2baf701b6af\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"Other\",\n \"Other\"\n ],\n \"effectiveLiftDate\": \"1892-10-05\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"license\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"1584-12-01\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1692-09-11\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"wa\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"9b0e2f4c-7c3e-44f7-b69c-da794983beca\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"Substandard Care or Patient Neglect/Abuse\",\n \"Fraud, Deception, or Misrepresentation\"\n ],\n \"effectiveLiftDate\": \"1779-12-21\",\n \"liftingUser\": \"\"\n }\n ]\n },\n {\n \"compact\": \"socw\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"2614-02-18\",\n \"dateOfIssuance\": \"1176-09-31\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdiction\": \"co\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseScope\": \"single-state\",\n \"licenseStatus\": \"inactive\",\n \"licenseType\": \"\",\n \"providerId\": \"43da7025-d2f0-4a33-a3ac-63d9822cefcd\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"investigations\": [\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"wa\",\n \"licenseType\": \"\",\n \"providerId\": \"056df11e-e52c-4d75-b3cc-cad9052b2344\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"socw\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"wa\",\n \"licenseType\": \"\",\n \"providerId\": \"09dc62b4-9d60-4f31-8fee-5f4ce2c6a0be\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"dateOfRenewal\": \"2472-05-30\",\n \"investigationStatus\": \"underInvestigation\",\n \"phoneNumber\": \"+72464522486\",\n \"licenseStatusName\": \"\",\n \"middleName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"privilege\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"2887-01-07\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1605-05-04\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"va\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"cea13954-176f-4b73-8a3b-ad475eeb57df\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"Substandard Care or Patient Neglect/Abuse\",\n \"Fraud, Deception, or Misrepresentation\"\n ],\n \"effectiveLiftDate\": \"1827-05-22\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"privilege\",\n \"adverseActionId\": \"\",\n \"compact\": \"socw\",\n \"creationDate\": \"1532-04-31\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1550-08-26\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"wa\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"117f1560-f5ab-42fb-914e-8f4573febac8\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"Substandard Care or Patient Neglect/Abuse\",\n \"Other\"\n ],\n \"effectiveLiftDate\": \"1642-10-27\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"middleName\": \"\",\n \"compactConnectRegisteredEmailAddress\": \"\"\n }\n ],\n \"total\": {\n \"value\": \"\",\n \"relation\": \"gte\"\n },\n \"lastSort\": \"\"\n}", "code": 200, "cookie": [], "header": [ diff --git a/backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/common.py b/backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/common.py index 679ff50453..3976b6bd4b 100644 --- a/backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/common.py +++ b/backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/common.py @@ -404,9 +404,15 @@ def license_sk_suffix(jurisdiction: str, license_type_abbr: str, license_scope: class ClinicalPrivilegeActionCategory(CCEnum): """Enum for adverse action clinical privilege action categories.""" - FRAUD = 'fraud' - CONSUMER_HARM = 'consumer harm' - OTHER = 'other' + NON_COMPLIANCE = 'Non-Compliance With Requirements' + CONFLICT_OF_INTEREST = 'Conflict of Interest' + SUBSTANDARD_CARE = 'Substandard Care or Patient Neglect/Abuse' + CRIMINAL_CONVICTION = 'Criminal Conviction or Adjudication' + CONFIDENTIALITY_VIOLATION = 'Confidentiality, Consent or Disclosure Violations' + FRAUD = 'Fraud, Deception, or Misrepresentation' + IMPROPER_SUPERVISION = 'Improper Supervision or Allowing Unlicensed Practice' + IMPROPER_PRESCRIBING = 'Improper Prescribing, Dispensing, Administering Medication/Drug Violation' + OTHER = 'Other' class ChangeHashMixin: diff --git a/backend/social-work-app/lambdas/python/common/common_test/test_constants.py b/backend/social-work-app/lambdas/python/common/common_test/test_constants.py index 6547692b93..d343d88163 100644 --- a/backend/social-work-app/lambdas/python/common/common_test/test_constants.py +++ b/backend/social-work-app/lambdas/python/common/common_test/test_constants.py @@ -95,7 +95,7 @@ DEFAULT_ACTION_AGAINST_PRIVILEGE = 'privilege' DEFAULT_BLOCKS_FUTURE_PRIVILEGES = True DEFAULT_ENCUMBRANCE_TYPE = 'suspension' -DEFAULT_CLINICAL_PRIVILEGE_ACTION_CATEGORY = 'fraud' +DEFAULT_CLINICAL_PRIVILEGE_ACTION_CATEGORY = 'Fraud, Deception, or Misrepresentation' DEFAULT_CREATION_EFFECTIVE_DATE = '2024-02-15' DEFAULT_CREATION_DATE = '2024-02-15T10:30:00+00:00' DEFAULT_AA_SUBMITTING_USER_ID = '12a6377e-c3a5-40e5-bca5-317ec854c556' diff --git a/backend/social-work-app/lambdas/python/common/tests/resources/api/adverse-action-post.json b/backend/social-work-app/lambdas/python/common/tests/resources/api/adverse-action-post.json index d7ecff1b8c..2d1ce71187 100644 --- a/backend/social-work-app/lambdas/python/common/tests/resources/api/adverse-action-post.json +++ b/backend/social-work-app/lambdas/python/common/tests/resources/api/adverse-action-post.json @@ -1,5 +1,5 @@ { "encumbranceEffectiveDate": "2023-01-15", "encumbranceType": "suspension", - "clinicalPrivilegeActionCategories": ["fraud"] + "clinicalPrivilegeActionCategories": ["Fraud, Deception, or Misrepresentation"] } diff --git a/backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py b/backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py index 9eace2cf05..83f8072f4d 100644 --- a/backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py +++ b/backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py @@ -111,7 +111,7 @@ def test_adverse_action_data_class_outputs_expected_database_object(self): 'actionAgainst': 'privilege', 'adverseActionId': '98765432-9876-9876-9876-987654321098', 'encumbranceType': 'suspension', - 'clinicalPrivilegeActionCategories': ['fraud'], + 'clinicalPrivilegeActionCategories': ['Fraud, Deception, or Misrepresentation'], 'compact': 'socw', 'creationDate': '2024-11-08T23:59:59+00:00', 'effectiveStartDate': '2024-02-15', diff --git a/backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_investigation.py b/backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_investigation.py index d147c8320d..c741f014b3 100644 --- a/backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_investigation.py +++ b/backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_investigation.py @@ -145,7 +145,7 @@ def test_validate_patch_with_encumbrance(self): 'encumbrance': { 'encumbranceEffectiveDate': '2024-03-15', 'encumbranceType': 'suspension', - 'clinicalPrivilegeActionCategories': ['consumer harm'], + 'clinicalPrivilegeActionCategories': ['Substandard Care or Patient Neglect/Abuse'], } } result = InvestigationPatchRequestSchema().load(investigation_data) diff --git a/backend/social-work-app/lambdas/python/search/tests/function/test_public_search_providers.py b/backend/social-work-app/lambdas/python/search/tests/function/test_public_search_providers.py index 1380fb7751..474d87ba94 100644 --- a/backend/social-work-app/lambdas/python/search/tests/function/test_public_search_providers.py +++ b/backend/social-work-app/lambdas/python/search/tests/function/test_public_search_providers.py @@ -199,7 +199,7 @@ def _generate_unlifted_license_adverse_action(self, *, provider_id: str) -> dict 'adverseActionId': 'aa-license-unlifted', 'dateOfUpdate': '2024-01-02T00:00:00+00:00', 'encumbranceType': 'suspension', - 'clinicalPrivilegeActionCategories': ['fraud'], + 'clinicalPrivilegeActionCategories': ['Fraud, Deception, or Misrepresentation'], 'submittingUser': {'userId': 'staff-1'}, } @@ -791,7 +791,7 @@ def test_license_eligibility_eligible_when_no_unlifted_adverse_action_on_license 'adverseActionId': 'aa-unlifted', 'dateOfUpdate': '2024-01-02T00:00:00+00:00', 'encumbranceType': 'suspension', - 'clinicalPrivilegeActionCategories': ['fraud'], + 'clinicalPrivilegeActionCategories': ['Fraud, Deception, or Misrepresentation'], 'submittingUser': {'userId': 'staff-1'}, } mock_hit = self._create_mock_hit( @@ -869,7 +869,7 @@ def test_license_eligibility_set_to_ineligible_if_unlifted_adverse_action_on_pri 'adverseActionId': 'aa-priv-unlifted', 'dateOfUpdate': '2024-01-02T00:00:00+00:00', 'encumbranceType': 'suspension', - 'clinicalPrivilegeActionCategories': ['fraud'], + 'clinicalPrivilegeActionCategories': ['Fraud, Deception, or Misrepresentation'], 'submittingUser': {'userId': 'staff-1'}, } nested = self._minimal_opensearch_license( @@ -966,7 +966,7 @@ def test_license_eligibility_eligible_when_no_blocking_factors(self, mock_opense 'dateOfUpdate': '2024-06-01T00:00:00+00:00', 'effectiveLiftDate': '2024-06-01', 'encumbranceType': 'suspension', - 'clinicalPrivilegeActionCategories': ['fraud'], + 'clinicalPrivilegeActionCategories': ['Fraud, Deception, or Misrepresentation'], 'submittingUser': {'userId': 'staff-1'}, } mock_hit = self._create_mock_hit( diff --git a/backend/social-work-app/stacks/api_stack/v1_api/api_model.py b/backend/social-work-app/stacks/api_stack/v1_api/api_model.py index d36f872875..705571da3b 100644 --- a/backend/social-work-app/stacks/api_stack/v1_api/api_model.py +++ b/backend/social-work-app/stacks/api_stack/v1_api/api_model.py @@ -784,7 +784,17 @@ def _clinical_privilege_action_categories_schema(self) -> JsonSchema: description='The categories of clinical privilege action', items=JsonSchema( type=JsonSchemaType.STRING, - enum=['fraud', 'consumer harm', 'other'], + enum=[ + 'Non-Compliance With Requirements', + 'Conflict of Interest', + 'Substandard Care or Patient Neglect/Abuse', + 'Criminal Conviction or Adjudication', + 'Confidentiality, Consent or Disclosure Violations', + 'Fraud, Deception, or Misrepresentation', + 'Improper Supervision or Allowing Unlicensed Practice', + 'Improper Prescribing, Dispensing, Administering Medication/Drug Violation', + 'Other', + ], ), ) diff --git a/backend/social-work-app/stacks/search_api_stack/v1_api/api_model.py b/backend/social-work-app/stacks/search_api_stack/v1_api/api_model.py index defbce2acc..af20464586 100644 --- a/backend/social-work-app/stacks/search_api_stack/v1_api/api_model.py +++ b/backend/social-work-app/stacks/search_api_stack/v1_api/api_model.py @@ -376,7 +376,17 @@ def _adverse_action_general_schema(self): type=JsonSchemaType.ARRAY, items=JsonSchema( type=JsonSchemaType.STRING, - enum=['fraud', 'consumer harm', 'other'], + enum=[ + 'Non-Compliance With Requirements', + 'Conflict of Interest', + 'Substandard Care or Patient Neglect/Abuse', + 'Criminal Conviction or Adjudication', + 'Confidentiality, Consent or Disclosure Violations', + 'Fraud, Deception, or Misrepresentation', + 'Improper Supervision or Allowing Unlicensed Practice', + 'Improper Prescribing, Dispensing, Administering Medication/Drug Violation', + 'Other', + ], ), ), 'liftingUser': JsonSchema(type=JsonSchemaType.STRING), diff --git a/backend/social-work-app/tests/resources/snapshots/GET_PROVIDER_RESPONSE_SCHEMA.json b/backend/social-work-app/tests/resources/snapshots/GET_PROVIDER_RESPONSE_SCHEMA.json index b57d5f5381..d37a400534 100644 --- a/backend/social-work-app/tests/resources/snapshots/GET_PROVIDER_RESPONSE_SCHEMA.json +++ b/backend/social-work-app/tests/resources/snapshots/GET_PROVIDER_RESPONSE_SCHEMA.json @@ -123,9 +123,15 @@ "description": "The categories of clinical privilege action", "items": { "enum": [ - "fraud", - "consumer harm", - "other" + "Non-Compliance With Requirements", + "Conflict of Interest", + "Substandard Care or Patient Neglect/Abuse", + "Criminal Conviction or Adjudication", + "Confidentiality, Consent or Disclosure Violations", + "Fraud, Deception, or Misrepresentation", + "Improper Supervision or Allowing Unlicensed Practice", + "Improper Prescribing, Dispensing, Administering Medication/Drug Violation", + "Other" ], "type": "string" }, @@ -783,9 +789,15 @@ "description": "The categories of clinical privilege action", "items": { "enum": [ - "fraud", - "consumer harm", - "other" + "Non-Compliance With Requirements", + "Conflict of Interest", + "Substandard Care or Patient Neglect/Abuse", + "Criminal Conviction or Adjudication", + "Confidentiality, Consent or Disclosure Violations", + "Fraud, Deception, or Misrepresentation", + "Improper Supervision or Allowing Unlicensed Practice", + "Improper Prescribing, Dispensing, Administering Medication/Drug Violation", + "Other" ], "type": "string" }, @@ -1643,9 +1655,15 @@ "description": "The categories of clinical privilege action", "items": { "enum": [ - "fraud", - "consumer harm", - "other" + "Non-Compliance With Requirements", + "Conflict of Interest", + "Substandard Care or Patient Neglect/Abuse", + "Criminal Conviction or Adjudication", + "Confidentiality, Consent or Disclosure Violations", + "Fraud, Deception, or Misrepresentation", + "Improper Supervision or Allowing Unlicensed Practice", + "Improper Prescribing, Dispensing, Administering Medication/Drug Violation", + "Other" ], "type": "string" }, diff --git a/backend/social-work-app/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json b/backend/social-work-app/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json index c4bdf7eb7f..600544728e 100644 --- a/backend/social-work-app/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json +++ b/backend/social-work-app/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json @@ -28,9 +28,15 @@ "description": "The categories of clinical privilege action", "items": { "enum": [ - "fraud", - "consumer harm", - "other" + "Non-Compliance With Requirements", + "Conflict of Interest", + "Substandard Care or Patient Neglect/Abuse", + "Criminal Conviction or Adjudication", + "Confidentiality, Consent or Disclosure Violations", + "Fraud, Deception, or Misrepresentation", + "Improper Supervision or Allowing Unlicensed Practice", + "Improper Prescribing, Dispensing, Administering Medication/Drug Violation", + "Other" ], "type": "string" }, diff --git a/backend/social-work-app/tests/resources/snapshots/PATCH_LICENSE_INVESTIGATION_REQUEST_SCHEMA.json b/backend/social-work-app/tests/resources/snapshots/PATCH_LICENSE_INVESTIGATION_REQUEST_SCHEMA.json index 76c5f58fa2..e06e1a7e9d 100644 --- a/backend/social-work-app/tests/resources/snapshots/PATCH_LICENSE_INVESTIGATION_REQUEST_SCHEMA.json +++ b/backend/social-work-app/tests/resources/snapshots/PATCH_LICENSE_INVESTIGATION_REQUEST_SCHEMA.json @@ -43,9 +43,15 @@ "description": "The categories of clinical privilege action", "items": { "enum": [ - "fraud", - "consumer harm", - "other" + "Non-Compliance With Requirements", + "Conflict of Interest", + "Substandard Care or Patient Neglect/Abuse", + "Criminal Conviction or Adjudication", + "Confidentiality, Consent or Disclosure Violations", + "Fraud, Deception, or Misrepresentation", + "Improper Supervision or Allowing Unlicensed Practice", + "Improper Prescribing, Dispensing, Administering Medication/Drug Violation", + "Other" ], "type": "string" }, diff --git a/backend/social-work-app/tests/resources/snapshots/PATCH_PRIVILEGE_INVESTIGATION_REQUEST_SCHEMA.json b/backend/social-work-app/tests/resources/snapshots/PATCH_PRIVILEGE_INVESTIGATION_REQUEST_SCHEMA.json index 0078eabf52..260833146a 100644 --- a/backend/social-work-app/tests/resources/snapshots/PATCH_PRIVILEGE_INVESTIGATION_REQUEST_SCHEMA.json +++ b/backend/social-work-app/tests/resources/snapshots/PATCH_PRIVILEGE_INVESTIGATION_REQUEST_SCHEMA.json @@ -29,9 +29,15 @@ "description": "The categories of clinical privilege action", "items": { "enum": [ - "fraud", - "consumer harm", - "other" + "Non-Compliance With Requirements", + "Conflict of Interest", + "Substandard Care or Patient Neglect/Abuse", + "Criminal Conviction or Adjudication", + "Confidentiality, Consent or Disclosure Violations", + "Fraud, Deception, or Misrepresentation", + "Improper Supervision or Allowing Unlicensed Practice", + "Improper Prescribing, Dispensing, Administering Medication/Drug Violation", + "Other" ], "type": "string" }, diff --git a/backend/social-work-app/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json b/backend/social-work-app/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json index 7aea9a0f24..a5159cfd90 100644 --- a/backend/social-work-app/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json +++ b/backend/social-work-app/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json @@ -21,9 +21,15 @@ "description": "The categories of clinical privilege action", "items": { "enum": [ - "fraud", - "consumer harm", - "other" + "Non-Compliance With Requirements", + "Conflict of Interest", + "Substandard Care or Patient Neglect/Abuse", + "Criminal Conviction or Adjudication", + "Confidentiality, Consent or Disclosure Violations", + "Fraud, Deception, or Misrepresentation", + "Improper Supervision or Allowing Unlicensed Practice", + "Improper Prescribing, Dispensing, Administering Medication/Drug Violation", + "Other" ], "type": "string" }, diff --git a/backend/social-work-app/tests/resources/snapshots/PROVIDER_USER_RESPONSE_SCHEMA.json b/backend/social-work-app/tests/resources/snapshots/PROVIDER_USER_RESPONSE_SCHEMA.json index e1bf46e275..deddce665b 100644 --- a/backend/social-work-app/tests/resources/snapshots/PROVIDER_USER_RESPONSE_SCHEMA.json +++ b/backend/social-work-app/tests/resources/snapshots/PROVIDER_USER_RESPONSE_SCHEMA.json @@ -612,9 +612,15 @@ "description": "The categories of clinical privilege action", "items": { "enum": [ - "fraud", - "consumer harm", - "other" + "Non-Compliance With Requirements", + "Conflict of Interest", + "Substandard Care or Patient Neglect/Abuse", + "Criminal Conviction or Adjudication", + "Confidentiality, Consent or Disclosure Violations", + "Fraud, Deception, or Misrepresentation", + "Improper Supervision or Allowing Unlicensed Practice", + "Improper Prescribing, Dispensing, Administering Medication/Drug Violation", + "Other" ], "type": "string" }, @@ -1531,9 +1537,15 @@ "description": "The categories of clinical privilege action", "items": { "enum": [ - "fraud", - "consumer harm", - "other" + "Non-Compliance With Requirements", + "Conflict of Interest", + "Substandard Care or Patient Neglect/Abuse", + "Criminal Conviction or Adjudication", + "Confidentiality, Consent or Disclosure Violations", + "Fraud, Deception, or Misrepresentation", + "Improper Supervision or Allowing Unlicensed Practice", + "Improper Prescribing, Dispensing, Administering Medication/Drug Violation", + "Other" ], "type": "string" }, diff --git a/backend/social-work-app/tests/smoke/encumbrance_smoke_tests.py b/backend/social-work-app/tests/smoke/encumbrance_smoke_tests.py index e136219765..4e06153dca 100644 --- a/backend/social-work-app/tests/smoke/encumbrance_smoke_tests.py +++ b/backend/social-work-app/tests/smoke/encumbrance_smoke_tests.py @@ -464,7 +464,7 @@ def test_license_encumbrance_workflow(): 'licenseScope': helper.license_scope, 'encumbranceEffectiveDate': '2024-11-11', 'encumbranceType': 'surrender of license', - 'clinicalPrivilegeActionCategories': ['fraud'], + 'clinicalPrivilegeActionCategories': ['Fraud, Deception, or Misrepresentation'], } # First encumbrance @@ -522,7 +522,7 @@ def test_license_encumbrance_workflow(): 'licenseScope': helper.license_scope, 'encumbranceEffectiveDate': '2025-01-01', 'encumbranceType': 'suspension', - 'clinicalPrivilegeActionCategories': ['consumer harm'], + 'clinicalPrivilegeActionCategories': ['Substandard Care or Patient Neglect/Abuse'], } helper.encumber_license(second_encumbrance_body) logger.info('Second license encumbrance created successfully') @@ -546,7 +546,7 @@ def test_license_encumbrance_workflow(): privilege_encumbrance_body = { 'encumbranceEffectiveDate': '2025-05-09', 'encumbranceType': 'suspension', - 'clinicalPrivilegeActionCategories': ['other'], + 'clinicalPrivilegeActionCategories': ['Other'], } helper.encumber_privilege(privilege_encumbrance_body) @@ -657,7 +657,7 @@ def test_privilege_encumbrance_workflow(): encumbrance_body = { 'encumbranceEffectiveDate': '2024-12-12', 'encumbranceType': 'revocation', - 'clinicalPrivilegeActionCategories': ['fraud'], + 'clinicalPrivilegeActionCategories': ['Fraud, Deception, or Misrepresentation'], } # First encumbrance @@ -684,7 +684,7 @@ def test_privilege_encumbrance_workflow(): second_encumbrance_body = { 'encumbranceEffectiveDate': '2025-02-02', 'encumbranceType': 'suspension', - 'clinicalPrivilegeActionCategories': ['consumer harm'], + 'clinicalPrivilegeActionCategories': ['Substandard Care or Patient Neglect/Abuse'], } helper.encumber_privilege(second_encumbrance_body) logger.info('Second privilege encumbrance created successfully') diff --git a/backend/social-work-app/tests/smoke/investigation_smoke_tests.py b/backend/social-work-app/tests/smoke/investigation_smoke_tests.py index 5a9c3ebd77..0a9d842461 100755 --- a/backend/social-work-app/tests/smoke/investigation_smoke_tests.py +++ b/backend/social-work-app/tests/smoke/investigation_smoke_tests.py @@ -478,7 +478,7 @@ def test_close_privilege_investigation_with_encumbrance(auth_headers): 'encumbrance': { 'encumbranceEffectiveDate': '2024-01-15', 'encumbranceType': 'revocation', - 'clinicalPrivilegeActionCategories': ['consumer harm'], + 'clinicalPrivilegeActionCategories': ['Substandard Care or Patient Neglect/Abuse'], }, } From 1d0617797a1349366102f5ba828f3f5ae4b893aa Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Tue, 28 Jul 2026 10:28:37 -0500 Subject: [PATCH 02/14] Update encumbrance type values --- .../api-specification/latest-oas30.json | 56 +++++++++++++++++-- .../cc_common/data_model/schema/common.py | 12 ++++ .../stacks/api_stack/v1_api/api_model.py | 12 ++++ .../LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json | 14 ++++- ..._LICENSE_INVESTIGATION_REQUEST_SCHEMA.json | 14 ++++- ...RIVILEGE_INVESTIGATION_REQUEST_SCHEMA.json | 14 ++++- .../PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json | 14 ++++- 7 files changed, 128 insertions(+), 8 deletions(-) diff --git a/backend/social-work-app/docs/internal/api-specification/latest-oas30.json b/backend/social-work-app/docs/internal/api-specification/latest-oas30.json index 5789979bd6..7528c8772d 100644 --- a/backend/social-work-app/docs/internal/api-specification/latest-oas30.json +++ b/backend/social-work-app/docs/internal/api-specification/latest-oas30.json @@ -4097,9 +4097,21 @@ "type": "string", "description": "The type of encumbrance", "enum": [ + "fine", + "reprimand", + "required supervision", + "completion of continuing education", + "public reprimand", + "probation", + "injunctive action", "suspension", "revocation", - "surrender of license" + "denial", + "surrender of license", + "modification of previous action-extension", + "modification of previous action-reduction", + "other monitoring", + "other adjudicated action not listed" ] } }, @@ -4222,9 +4234,21 @@ "type": "string", "description": "The type of encumbrance", "enum": [ + "fine", + "reprimand", + "required supervision", + "completion of continuing education", + "public reprimand", + "probation", + "injunctive action", "suspension", "revocation", - "surrender of license" + "denial", + "surrender of license", + "modification of previous action-extension", + "modification of previous action-reduction", + "other monitoring", + "other adjudicated action not listed" ] }, "licenseScope": { @@ -4367,9 +4391,21 @@ "type": "string", "description": "The type of encumbrance", "enum": [ + "fine", + "reprimand", + "required supervision", + "completion of continuing education", + "public reprimand", + "probation", + "injunctive action", "suspension", "revocation", - "surrender of license" + "denial", + "surrender of license", + "modification of previous action-extension", + "modification of previous action-reduction", + "other monitoring", + "other adjudicated action not listed" ] }, "licenseScope": { @@ -4421,9 +4457,21 @@ "type": "string", "description": "The type of encumbrance", "enum": [ + "fine", + "reprimand", + "required supervision", + "completion of continuing education", + "public reprimand", + "probation", + "injunctive action", "suspension", "revocation", - "surrender of license" + "denial", + "surrender of license", + "modification of previous action-extension", + "modification of previous action-reduction", + "other monitoring", + "other adjudicated action not listed" ] } }, diff --git a/backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/common.py b/backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/common.py index 3976b6bd4b..d83edc4645 100644 --- a/backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/common.py +++ b/backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/common.py @@ -381,9 +381,21 @@ class EncumbranceType(CCEnum): Enum for the allowed types of encumbrances """ + FINE = 'fine' + REPRIMAND = 'reprimand' + REQUIRED_SUPERVISION = 'required supervision' + COMPLETION_OF_CONTINUING_EDUCATION = 'completion of continuing education' + PUBLIC_REPRIMAND = 'public reprimand' + PROBATION = 'probation' + INJUNCTIVE_ACTION = 'injunctive action' SUSPENSION = 'suspension' REVOCATION = 'revocation' + DENIAL = 'denial' SURRENDER_OF_LICENSE = 'surrender of license' + MODIFICATION_OF_PREVIOUS_ACTION_EXTENSION = 'modification of previous action-extension' + MODIFICATION_OF_PREVIOUS_ACTION_REDUCTION = 'modification of previous action-reduction' + OTHER_MONITORING = 'other monitoring' + OTHER_ADJUDICATED_ACTION_NOT_LISTED = 'other adjudicated action not listed' class LicenseScopeEnum(CCEnum): diff --git a/backend/social-work-app/stacks/api_stack/v1_api/api_model.py b/backend/social-work-app/stacks/api_stack/v1_api/api_model.py index 705571da3b..cf8e150eb3 100644 --- a/backend/social-work-app/stacks/api_stack/v1_api/api_model.py +++ b/backend/social-work-app/stacks/api_stack/v1_api/api_model.py @@ -854,9 +854,21 @@ def _encumbrance_type_schema(self) -> JsonSchema: type=JsonSchemaType.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', ], ) diff --git a/backend/social-work-app/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json b/backend/social-work-app/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json index 600544728e..5218ec23fd 100644 --- a/backend/social-work-app/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json +++ b/backend/social-work-app/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json @@ -18,9 +18,21 @@ "encumbranceType": { "description": "The type of encumbrance", "enum": [ + "fine", + "reprimand", + "required supervision", + "completion of continuing education", + "public reprimand", + "probation", + "injunctive action", "suspension", "revocation", - "surrender of license" + "denial", + "surrender of license", + "modification of previous action-extension", + "modification of previous action-reduction", + "other monitoring", + "other adjudicated action not listed" ], "type": "string" }, diff --git a/backend/social-work-app/tests/resources/snapshots/PATCH_LICENSE_INVESTIGATION_REQUEST_SCHEMA.json b/backend/social-work-app/tests/resources/snapshots/PATCH_LICENSE_INVESTIGATION_REQUEST_SCHEMA.json index e06e1a7e9d..bc958df381 100644 --- a/backend/social-work-app/tests/resources/snapshots/PATCH_LICENSE_INVESTIGATION_REQUEST_SCHEMA.json +++ b/backend/social-work-app/tests/resources/snapshots/PATCH_LICENSE_INVESTIGATION_REQUEST_SCHEMA.json @@ -33,9 +33,21 @@ "encumbranceType": { "description": "The type of encumbrance", "enum": [ + "fine", + "reprimand", + "required supervision", + "completion of continuing education", + "public reprimand", + "probation", + "injunctive action", "suspension", "revocation", - "surrender of license" + "denial", + "surrender of license", + "modification of previous action-extension", + "modification of previous action-reduction", + "other monitoring", + "other adjudicated action not listed" ], "type": "string" }, diff --git a/backend/social-work-app/tests/resources/snapshots/PATCH_PRIVILEGE_INVESTIGATION_REQUEST_SCHEMA.json b/backend/social-work-app/tests/resources/snapshots/PATCH_PRIVILEGE_INVESTIGATION_REQUEST_SCHEMA.json index 260833146a..c58aa46f55 100644 --- a/backend/social-work-app/tests/resources/snapshots/PATCH_PRIVILEGE_INVESTIGATION_REQUEST_SCHEMA.json +++ b/backend/social-work-app/tests/resources/snapshots/PATCH_PRIVILEGE_INVESTIGATION_REQUEST_SCHEMA.json @@ -19,9 +19,21 @@ "encumbranceType": { "description": "The type of encumbrance", "enum": [ + "fine", + "reprimand", + "required supervision", + "completion of continuing education", + "public reprimand", + "probation", + "injunctive action", "suspension", "revocation", - "surrender of license" + "denial", + "surrender of license", + "modification of previous action-extension", + "modification of previous action-reduction", + "other monitoring", + "other adjudicated action not listed" ], "type": "string" }, diff --git a/backend/social-work-app/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json b/backend/social-work-app/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json index a5159cfd90..948ba2976e 100644 --- a/backend/social-work-app/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json +++ b/backend/social-work-app/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json @@ -11,9 +11,21 @@ "encumbranceType": { "description": "The type of encumbrance", "enum": [ + "fine", + "reprimand", + "required supervision", + "completion of continuing education", + "public reprimand", + "probation", + "injunctive action", "suspension", "revocation", - "surrender of license" + "denial", + "surrender of license", + "modification of previous action-extension", + "modification of previous action-reduction", + "other monitoring", + "other adjudicated action not listed" ], "type": "string" }, From 074af68eb9f69afe7bdc0c32bca651d9c74a4f80 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Tue, 28 Jul 2026 13:09:32 -0500 Subject: [PATCH 03/14] update non-prod OpenSearch node count --- .../search_persistent_stack/index_manager.py | 6 +- .../provider_search_domain.py | 73 ++++++------------- .../tests/app/test_search_persistent_stack.py | 47 +++++------- 3 files changed, 45 insertions(+), 81 deletions(-) diff --git a/backend/social-work-app/stacks/search_persistent_stack/index_manager.py b/backend/social-work-app/stacks/search_persistent_stack/index_manager.py index 21c8e1512b..e395d36f79 100644 --- a/backend/social-work-app/stacks/search_persistent_stack/index_manager.py +++ b/backend/social-work-app/stacks/search_persistent_stack/index_manager.py @@ -15,9 +15,11 @@ from stacks.vpc_stack import VpcStack # Index configuration constants -# Non-prod environments use a single data node, so no replicas are needed +# Both prod and non-prod use 3 data nodes across 3 AZs +# With 1 primary shard and 2 replicas, each node holds one copy of the data +# This ensures proper quorum for primary election and data availability if a node fails NON_PROD_NUMBER_OF_SHARDS = 1 -NON_PROD_NUMBER_OF_REPLICAS = 0 +NON_PROD_NUMBER_OF_REPLICAS = 2 # Production uses 3 data nodes across 3 AZs, so 1 primary and 2 replica ensures data availability # if this is updated, the total of primary + replica shards must be a multiple of 3 PROD_NUMBER_OF_SHARDS = 1 diff --git a/backend/social-work-app/stacks/search_persistent_stack/provider_search_domain.py b/backend/social-work-app/stacks/search_persistent_stack/provider_search_domain.py index 7de6832b1a..187552620b 100644 --- a/backend/social-work-app/stacks/search_persistent_stack/provider_search_domain.py +++ b/backend/social-work-app/stacks/search_persistent_stack/provider_search_domain.py @@ -22,7 +22,7 @@ from common_constructs.stack import Stack from constructs import Construct -from stacks.vpc_stack import PRIVATE_SUBNET_ONE_NAME, VpcStack +from stacks.vpc_stack import VpcStack PROD_EBS_VOLUME_SIZE = 25 NON_PROD_EBS_VOLUME_SIZE = 10 @@ -40,8 +40,8 @@ class ProviderSearchDomain(Construct): - CloudWatch alarms for capacity monitoring Instance sizing by environment: - - Non-prod (sandbox/test/beta): t3.small.search, 1 node - - Prod: m7g.medium.search, 3 master + 3 data nodes (with standby) + - Non-prod (sandbox/test/beta): t3.small.search, 3 data nodes across 3 AZs + - Prod: m7g.medium.search, 3 master + 3 data nodes across 3 AZs (with standby) """ def __init__( @@ -153,7 +153,7 @@ def __init__( # Determine AZ awareness based on environment zone_awareness_config = self._get_zone_awareness_config() # Determine subnet selection based on environment - self.vpc_subnets = self._get_vpc_subnets(vpc_stack) + self.vpc_subnets = self._get_vpc_subnets() # Create OpenSearch Domain self.domain = Domain( @@ -289,7 +289,7 @@ def _get_capacity_config(self) -> CapacityConfig: """ Determine OpenSearch cluster capacity configuration based on environment. - Non-prod (sandbox, test, beta, etc.): Single t3.small.search node + Non-prod (sandbox, test, beta, etc.): 3 t3.small.search nodes across 3 AZs Prod: 3 dedicated master (r8g.medium.search) + 3 data nodes (m7g.medium.search) with standby :return: CapacityConfig with appropriate instance types and counts @@ -313,14 +313,17 @@ def _get_capacity_config(self) -> CapacityConfig: multi_az_with_standby_enabled=True, ) - # Single node configuration for all non-prod environments + # Three-node configuration for all non-prod environments # (test, beta, and developer sandboxes) + # Using 3 nodes provides proper quorum for primary election - with only + # 2 nodes, if the primary goes down, there's no quorum to elect a new one. + # 3 nodes also ensures data redundancy with replicas distributed across nodes. return CapacityConfig( data_node_instance_type='t3.small.search', - data_nodes=1, - # No dedicated master nodes for single-node clusters + data_nodes=3, + # No dedicated master nodes - data nodes handle cluster management master_nodes=None, - # No multi-AZ for single node + # No multi-AZ with standby (to reduce costs in non-prod) multi_az_with_standby_enabled=False, ) @@ -328,58 +331,26 @@ def _get_zone_awareness_config(self) -> ZoneAwarenessConfig: """ Determine OpenSearch cluster availability zone awareness based on environment. - 3 for production, not enabled for all other non-prod environments + Both prod and non-prod use 3 AZs for zone awareness to ensure proper + distribution of shards across availability zones. :return: ZoneAwarenessConfig with appropriate settings """ - if self._is_prod_environment: - return ZoneAwarenessConfig(enabled=True, availability_zone_count=3) - - # Non-prod environments only use one data node, hence we don't enable zone awareness - return ZoneAwarenessConfig(enabled=False) + # Both prod and non-prod use 3 data nodes across 3 AZs + return ZoneAwarenessConfig(enabled=True, availability_zone_count=3) - def _get_vpc_subnets(self, vpc_stack: VpcStack) -> SubnetSelection: + def _get_vpc_subnets(self) -> SubnetSelection: """ Determine VPC subnet selection based on environment. - Production: All private isolated subnets (3 AZs) for zone awareness and high availability - Non-prod: Single subnet (privateSubnet1 with CIDR 10.0.0.0/20) for single-node deployment + Both prod and non-prod use all 3 private isolated subnets for zone awareness + and proper distribution of data nodes across availability zones. - :param vpc_stack: The VPC stack containing the private subnets :return: SubnetSelection with appropriate subnet configuration """ - if self._is_prod_environment: - # Production: Use all private isolated subnets from the VPC. - # VPC is configured with max_azs=3, so this will select exactly 3 subnets - return SubnetSelection(subnet_type=SubnetType.PRIVATE_ISOLATED) - - # Non-prod: Single-node deployment explicitly uses privateSubnet1 (CIDR 10.0.0.0/20) - # OpenSearch requires exactly one subnet for single-node deployments - # We explicitly find the subnet by its construct name to guarantee consistency - private_subnet1 = self._find_subnet_by_name(vpc_stack.vpc, PRIVATE_SUBNET_ONE_NAME) - return SubnetSelection(subnets=[private_subnet1]) - - def _find_subnet_by_name(self, vpc, subnet_name: str): - """ - Find a specific subnet by its logical construct name in the VPC. - - This provides a guaranteed, explicit reference to a specific subnet regardless of - CDK's internal list ordering, which is critical for stateful resources like OpenSearch. - - :param vpc: The VPC construct containing the subnet - :param subnet_name: The logical name of the subnet (e.g., 'privateSubnet1') - :return: The ISubnet instance - :raises ValueError: If the subnet cannot be found - """ - # Navigate the construct tree to find the subnet by name - subnet_construct = vpc.node.try_find_child(subnet_name) - if subnet_construct is None: - raise ValueError( - f'Subnet {subnet_name} not found in VPC construct tree. ' - f'Available children: {[c.node.id for c in vpc.node.children]}' - ) - - return subnet_construct + # Both prod and non-prod use 3 data nodes across 3 AZs + # VPC is configured with max_azs=3, so this will select exactly 3 subnets + return SubnetSelection(subnet_type=SubnetType.PRIVATE_ISOLATED) def _add_capacity_alarms(self, alarm_topic: ITopic): """ diff --git a/backend/social-work-app/tests/app/test_search_persistent_stack.py b/backend/social-work-app/tests/app/test_search_persistent_stack.py index 1b57da7c23..6aca4c6dfa 100644 --- a/backend/social-work-app/tests/app/test_search_persistent_stack.py +++ b/backend/social-work-app/tests/app/test_search_persistent_stack.py @@ -136,13 +136,13 @@ def test_sandbox_instance_type(self): search_stack = self.app.sandbox_backend_stage.search_persistent_stack search_template = Template.from_stack(search_stack) - # Verify sandbox uses t3.small.search with single node + # Verify sandbox uses t3.small.search with 3 data nodes search_template.has_resource_properties( 'AWS::OpenSearchService::Domain', { 'ClusterConfig': { 'InstanceType': 't3.small.search', - 'InstanceCount': 1, + 'InstanceCount': 3, 'DedicatedMasterEnabled': False, 'MultiAZWithStandbyEnabled': False, }, @@ -258,16 +258,10 @@ def test_capacity_alarms_configured(self): def test_sandbox_uses_expected_private_subnet(self): """ - Test that the OpenSearch Domain in sandbox uses expected private Subnet. - - For non-prod single-node deployments, OpenSearch must use exactly one subnet. - We explicitly select privateSubnet1 (CIDR 10.0.0.0/20) to ensure deterministic - placement across deployments, since the related lambda functions will also be - deployed within that same subnet, and we want to ensure that can communicate with - one another. + Test that the OpenSearch Domain in sandbox uses expected private Subnets. - This test verifies that OpenSearch references the specific subnet we expect, - not just any arbitrary subnet from the VPC. + For non-prod three-node deployments, OpenSearch uses all 3 private isolated + subnets across 3 AZs for zone awareness and proper shard distribution. """ search_stack = self.app.sandbox_backend_stage.search_persistent_stack search_template = Template.from_stack(search_stack) @@ -278,23 +272,20 @@ def test_sandbox_uses_expected_private_subnet(self): vpc_options = opensearch_properties['VPCOptions'] subnet_ids = vpc_options['SubnetIds'] - # For sandbox (non-prod), should use exactly one subnet - self.assertEqual(len(subnet_ids), 1, 'Sandbox OpenSearch should use exactly one subnet') - - # Get the subnet reference from OpenSearch - opensearch_subnet_ref = subnet_ids[0] - # Extract the export name that OpenSearch is importing - import_value = opensearch_subnet_ref['Fn::ImportValue'] - - # Verify OpenSearch is importing the correct subnet (privateSubnet1) - # The import_value should reference the export name of privateSubnet1 - # The export name contains the construct name, which includes 'privateSubnet1' - self.assertIn( - 'privateSubnet1', - str(import_value), - f'OpenSearch should import privateSubnet1, but is importing: {import_value}. ' - 'This is critical for deterministic subnet placement in non-prod environments.', - ) + # For sandbox (non-prod), should use three subnets + self.assertEqual(len(subnet_ids), 3, 'Sandbox OpenSearch should use three subnets') + + # Get the subnet references for each AZ + for index, subnet_id in enumerate(subnet_ids): + # Extract the export name that OpenSearch is importing + import_value = subnet_id['Fn::ImportValue'] + # Verify OpenSearch is importing the correct subnet + self.assertIn( + f'privateSubnet{index + 1}', + str(import_value), + f'OpenSearch should import {subnet_id}, but is importing: {import_value}. ' + 'This is critical for deterministic subnet placement in non-prod environments.', + ) class TestProdSearchPersistentStack(TstAppABC, TestCase): From 7f2dec01770b515aa3ad91a1e7261c2daa14985a Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Tue, 28 Jul 2026 13:09:51 -0500 Subject: [PATCH 04/14] support multiple npdb categories --- .../data_model/schema/adverse_action/api.py | 5 +-- .../test_schema/test_adverse_action.py | 31 +++++++++++++++++++ .../test_handlers/test_encumbrance.py | 28 +++++++++++++++++ .../tests/smoke/encumbrance_smoke_tests.py | 25 ++++++++++++--- 4 files changed, 80 insertions(+), 9 deletions(-) diff --git a/backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py b/backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py index d24975cbad..d773c1303a 100644 --- a/backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py +++ b/backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py @@ -25,11 +25,8 @@ class AdverseActionPostRequestSchema(ForgivingSchema): encumbranceEffectiveDate = Date(required=True, allow_none=False) encumbranceType = EncumbranceTypeField(required=True, allow_none=False) - # in the case of Social Work, we only allow one category, but we are keeping this as a list for compatibility - # with the existing code base, and to allow the potential of supporting multiple categories should this be needed - # in the future clinicalPrivilegeActionCategories = List( - ClinicalPrivilegeActionCategoryField(), required=True, allow_none=False, validate=Length(equal=1) + ClinicalPrivilegeActionCategoryField(), required=True, allow_none=False, validate=Length(min=1) ) diff --git a/backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py b/backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py index 83f8072f4d..08496ad371 100644 --- a/backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py +++ b/backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py @@ -137,6 +137,37 @@ def test_validate_post(self): with open('tests/resources/api/adverse-action-post.json') as f: AdverseActionPostRequestSchema().load(json.load(f)) + def test_validate_post_with_multiple_categories(self): + """Test that multiple clinical privilege action categories are accepted""" + from cc_common.data_model.schema.adverse_action.api import AdverseActionPostRequestSchema + + with open('tests/resources/api/adverse-action-post.json') as f: + adverse_action_data = json.load(f) + adverse_action_data['clinicalPrivilegeActionCategories'] = [ + 'Fraud, Deception, or Misrepresentation', + 'Substandard Care or Patient Neglect/Abuse', + ] + + result = AdverseActionPostRequestSchema().load(adverse_action_data) + self.assertEqual( + [ + 'Fraud, Deception, or Misrepresentation', + 'Substandard Care or Patient Neglect/Abuse', + ], + result['clinicalPrivilegeActionCategories'], + ) + + def test_invalid_post_with_empty_categories(self): + """Test validation error when clinical privilege action categories list is empty""" + from cc_common.data_model.schema.adverse_action.api import AdverseActionPostRequestSchema + + with open('tests/resources/api/adverse-action-post.json') as f: + adverse_action_data = json.load(f) + adverse_action_data['clinicalPrivilegeActionCategories'] = [] + + with self.assertRaises(ValidationError): + AdverseActionPostRequestSchema().load(adverse_action_data) + def test_invalid_post(self): """Test validation error when required field is missing""" from cc_common.data_model.schema.adverse_action.api import AdverseActionPostRequestSchema diff --git a/backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py b/backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py index f0dbb0c23c..84ffa19c08 100644 --- a/backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py +++ b/backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py @@ -145,6 +145,34 @@ def test_privilege_encumbrance_handler_adds_adverse_action_record_in_provider_da loaded_adverse_action.to_dict(), ) + def test_privilege_encumbrance_handler_stores_multiple_clinical_privilege_action_categories(self): + from cc_common.data_model.schema.adverse_action import AdverseActionData + from cc_common.data_model.schema.common import ClinicalPrivilegeActionCategory + from handlers.encumbrance import encumbrance_handler + + categories = [ + ClinicalPrivilegeActionCategory.FRAUD.value, + ClinicalPrivilegeActionCategory.SUBSTANDARD_CARE.value, + ] + event, context = self._when_testing_privilege_encumbrance( + body_overrides={'clinicalPrivilegeActionCategories': categories} + ) + + response = encumbrance_handler(event, self.mock_context) + self.assertEqual(200, response['statusCode'], msg=json.loads(response['body'])) + + pk = f'{context["compact"]}#PROVIDER#{context["providerId"]}' + sk_prefix = ( + f'{context["compact"]}#PROVIDER#privilege/{context["jurisdiction"]}/lcsw/single-state#ADVERSE_ACTION' + ) + adverse_action_encumbrances = self._provider_table.query( + Select='ALL_ATTRIBUTES', + KeyConditionExpression=Key('pk').eq(pk) & Key('sk').begins_with(sk_prefix), + ) + self.assertEqual(1, len(adverse_action_encumbrances['Items'])) + loaded_adverse_action = AdverseActionData.from_database_record(adverse_action_encumbrances['Items'][0]) + self.assertEqual(categories, loaded_adverse_action.clinicalPrivilegeActionCategories) + def test_privilege_encumbrance_handler_sets_provider_record_to_encumbered_in_provider_data_table(self): from cc_common.data_model.schema.common import LicenseEncumberedStatusEnum from cc_common.data_model.schema.provider import ProviderData diff --git a/backend/social-work-app/tests/smoke/encumbrance_smoke_tests.py b/backend/social-work-app/tests/smoke/encumbrance_smoke_tests.py index 4e06153dca..c0be35fec3 100644 --- a/backend/social-work-app/tests/smoke/encumbrance_smoke_tests.py +++ b/backend/social-work-app/tests/smoke/encumbrance_smoke_tests.py @@ -464,7 +464,10 @@ def test_license_encumbrance_workflow(): 'licenseScope': helper.license_scope, 'encumbranceEffectiveDate': '2024-11-11', 'encumbranceType': 'surrender of license', - 'clinicalPrivilegeActionCategories': ['Fraud, Deception, or Misrepresentation'], + 'clinicalPrivilegeActionCategories': [ + 'Fraud, Deception, or Misrepresentation', + 'Substandard Care or Patient Neglect/Abuse', + ], } # First encumbrance @@ -522,7 +525,10 @@ def test_license_encumbrance_workflow(): 'licenseScope': helper.license_scope, 'encumbranceEffectiveDate': '2025-01-01', 'encumbranceType': 'suspension', - 'clinicalPrivilegeActionCategories': ['Substandard Care or Patient Neglect/Abuse'], + 'clinicalPrivilegeActionCategories': [ + 'Substandard Care or Patient Neglect/Abuse', + 'Conflict of Interest', + ], } helper.encumber_license(second_encumbrance_body) logger.info('Second license encumbrance created successfully') @@ -546,7 +552,10 @@ def test_license_encumbrance_workflow(): privilege_encumbrance_body = { 'encumbranceEffectiveDate': '2025-05-09', 'encumbranceType': 'suspension', - 'clinicalPrivilegeActionCategories': ['Other'], + 'clinicalPrivilegeActionCategories': [ + 'Other', + 'Non-Compliance With Requirements', + ], } helper.encumber_privilege(privilege_encumbrance_body) @@ -657,7 +666,10 @@ def test_privilege_encumbrance_workflow(): encumbrance_body = { 'encumbranceEffectiveDate': '2024-12-12', 'encumbranceType': 'revocation', - 'clinicalPrivilegeActionCategories': ['Fraud, Deception, or Misrepresentation'], + 'clinicalPrivilegeActionCategories': [ + 'Fraud, Deception, or Misrepresentation', + 'Criminal Conviction or Adjudication', + ], } # First encumbrance @@ -684,7 +696,10 @@ def test_privilege_encumbrance_workflow(): second_encumbrance_body = { 'encumbranceEffectiveDate': '2025-02-02', 'encumbranceType': 'suspension', - 'clinicalPrivilegeActionCategories': ['Substandard Care or Patient Neglect/Abuse'], + 'clinicalPrivilegeActionCategories': [ + 'Substandard Care or Patient Neglect/Abuse', + 'Improper Supervision or Allowing Unlicensed Practice', + ], } helper.encumber_privilege(second_encumbrance_body) logger.info('Second privilege encumbrance created successfully') From 178c330d91ac0694bab2d5693afae2ec9e7b49c8 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Tue, 28 Jul 2026 13:14:55 -0500 Subject: [PATCH 05/14] set proper number of shards when resetting OpenSearch indices --- .../python/search/handlers/populate_provider_documents.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/social-work-app/lambdas/python/search/handlers/populate_provider_documents.py b/backend/social-work-app/lambdas/python/search/handlers/populate_provider_documents.py index 7c14b8200d..c3c2e3cce4 100644 --- a/backend/social-work-app/lambdas/python/search/handlers/populate_provider_documents.py +++ b/backend/social-work-app/lambdas/python/search/handlers/populate_provider_documents.py @@ -25,7 +25,7 @@ indexing (uses numberOfShards / numberOfReplicas). Run during low traffic; do not combine with resumption (startingCompact / startingLastKey) for the same run. - numberOfShards: Primary shard count for recreated indexes (default: 1). -- numberOfReplicas: Replica shard count for recreated indexes (default: 0). +- numberOfReplicas: Replica shard count for recreated indexes (default: 2). Race Condition Consideration: A potential race condition can occur when running this function while provider @@ -84,7 +84,7 @@ def populate_provider_documents(event: dict, context: LambdaContext): - startingLastKey: The DynamoDB pagination key to resume from - resetIndexes: If true, delete and recreate all compact indexes first - numberOfShards: Shards for recreated indexes (default 1) - - numberOfReplicas: Replicas for recreated indexes (default 0) + - numberOfReplicas: Replicas for recreated indexes (default 2) :param context: Lambda context :return: Summary of indexing operation, including pagination info if incomplete """ @@ -93,7 +93,7 @@ def populate_provider_documents(event: dict, context: LambdaContext): reset_indexes = bool(event.get('resetIndexes', False)) number_of_shards = int(event.get('numberOfShards', 1)) - number_of_replicas = int(event.get('numberOfReplicas', 0)) + number_of_replicas = int(event.get('numberOfReplicas', 2)) if reset_indexes: # this reset functionality is only intended for development environments From 4eddf51829dc6da36e9fca9a8b37336e757ea2ad Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Tue, 28 Jul 2026 14:48:07 -0500 Subject: [PATCH 06/14] Return discipline information from public provider endpoint --- .../data_model/schema/license/api.py | 6 +- .../data_model/schema/privilege/api.py | 2 + .../data_model/schema/provider/api.py | 5 +- .../test_handlers/test_public_lookup.py | 49 ++++ .../stacks/api_stack/v1_api/api_model.py | 55 ++++ .../PUBLIC_GET_PROVIDER_RESPONSE_SCHEMA.json | 270 ++++++++++++++++++ 6 files changed, 384 insertions(+), 3 deletions(-) diff --git a/backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/license/api.py b/backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/license/api.py index 9fab989283..646edf1bda 100644 --- a/backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/license/api.py +++ b/backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/license/api.py @@ -10,7 +10,10 @@ from marshmallow.validate import Length from cc_common.config import config -from cc_common.data_model.schema.adverse_action.api import AdverseActionGeneralResponseSchema +from cc_common.data_model.schema.adverse_action.api import ( + AdverseActionGeneralResponseSchema, + AdverseActionPublicResponseSchema, +) from cc_common.data_model.schema.base_record import ForgivingSchema, StrictSchema from cc_common.data_model.schema.common import ( ActiveInactiveStatus, @@ -270,3 +273,4 @@ class LicensePublicResponseSchema(LicenseExpirationStatusMixin, ForgivingSchema) compactEligibility = CompactEligibility(required=True, allow_none=False) dateOfExpiration = Raw(required=True, allow_none=False) licenseNumber = String(required=True, allow_none=False, validate=Length(1, 100)) + adverseActions = List(Nested(AdverseActionPublicResponseSchema, required=False, allow_none=False)) diff --git a/backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/privilege/api.py b/backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/privilege/api.py index f08f35e0de..375552406a 100644 --- a/backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/privilege/api.py +++ b/backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/privilege/api.py @@ -4,6 +4,7 @@ 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 ( @@ -80,5 +81,6 @@ class PrivilegePublicResponseSchema(ForgivingSchema): licenseJurisdiction = Jurisdiction(required=True, allow_none=False) licenseType = String(required=True, allow_none=False) dateOfExpiration = Raw(required=True, allow_none=False) + adverseActions = List(Nested(AdverseActionPublicResponseSchema, required=False, allow_none=False)) administratorSetStatus = ActiveInactive(required=True, allow_none=False) status = ActiveInactive(required=True, allow_none=False) diff --git a/backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/provider/api.py b/backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/provider/api.py index a704c4ec7d..d450ca742a 100644 --- a/backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/provider/api.py +++ b/backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/provider/api.py @@ -194,8 +194,9 @@ class ProviderPublicResponseSchema(ForgivingSchema): familyName = String(required=True, allow_none=False, validate=Length(1, 100)) suffix = String(required=False, allow_none=False, validate=Length(1, 100)) - # Unlike the JCC public provider search, which only returns privilege data for a provider,Social Workreturns both - # licenses and privileges and does not return any adverse action data. + # Unlike the JCC public provider search, which only returns privilege data for a provider, Social Work returns + # both licenses and privileges. Adverse actions are also returned (nested under licenses/privileges), but with + # NPDB category and other staff-only fields stripped via AdverseActionPublicResponseSchema. licenses = List(Nested(LicensePublicResponseSchema(), required=False, allow_none=False)) privileges = List(Nested(PrivilegePublicResponseSchema(), required=False, allow_none=False)) diff --git a/backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_public_lookup.py b/backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_public_lookup.py index cb61f95d98..026b9b8caf 100644 --- a/backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_public_lookup.py +++ b/backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_public_lookup.py @@ -29,6 +29,7 @@ 'compactEligibility': 'eligible', 'dateOfExpiration': '2025-04-04', 'licenseNumber': 'B0608337260', + 'adverseActions': [], } ], 'privileges': [ @@ -40,6 +41,7 @@ 'licenseJurisdiction': 'oh', 'licenseType': 'licensed clinical social worker', 'dateOfExpiration': '2025-04-04', + 'adverseActions': [], 'administratorSetStatus': 'active', 'status': 'active', } @@ -74,6 +76,52 @@ def test_public_get_provider_response_with_expected_fields_filtered(self): self.assertEqual(EXPECTED_PROVIDER_RESPONSE, provider_data) + def test_public_get_provider_response_includes_adverse_actions_without_categories(self): + self._load_provider_data() + # a privilege adverse action, using the default jurisdiction/license type that match the default privilege + privilege_adverse_action = self.test_data_generator.put_default_adverse_action_record_in_provider_table() + # a license adverse action, matching the default multi-state license + license_adverse_action = self.test_data_generator.put_default_adverse_action_record_in_provider_table( + value_overrides={ + 'actionAgainst': 'license', + 'jurisdiction': 'oh', + 'licenseScope': 'multi-state', + 'adverseActionId': '11111111-1111-1111-1111-111111111111', + } + ) + + from handlers.public_lookup import public_get_provider + + with open('../common/tests/resources/api-event.json') as f: + event = json.load(f) + + # public endpoint does not have authorizer + del event['requestContext']['authorizer'] + event['pathParameters'] = {'compact': 'socw', 'providerId': '89a6377e-c3a5-40e5-bca5-317ec854c570'} + event['queryStringParameters'] = None + + resp = public_get_provider(event, self.mock_context) + + self.assertEqual(200, resp['statusCode']) + provider_data = json.loads(resp['body']) + + privilege_public_adverse_actions = provider_data['privileges'][0]['adverseActions'] + license_public_adverse_actions = provider_data['licenses'][0]['adverseActions'] + + self.assertEqual(1, len(privilege_public_adverse_actions)) + self.assertEqual(1, len(license_public_adverse_actions)) + + for adverse_action, expected_adverse_action_id in ( + (privilege_public_adverse_actions[0], str(privilege_adverse_action.adverseActionId)), + (license_public_adverse_actions[0], str(license_adverse_action.adverseActionId)), + ): + self.assertEqual(expected_adverse_action_id, adverse_action['adverseActionId']) + # NPDB categories and other staff-only fields must not be exposed publicly + self.assertNotIn('clinicalPrivilegeActionCategories', adverse_action) + self.assertNotIn('encumbranceType', adverse_action) + self.assertNotIn('submittingUser', adverse_action) + self.assertNotIn('liftingUser', adverse_action) + def test_public_get_provider_response_only_returns_most_recent_licenses(self): self._load_provider_data() # adding another license for same license type from another state, with an older issuance and renewal date @@ -164,6 +212,7 @@ def test_public_get_provider_response_returns_multiple_license_types(self): 'compactEligibility': 'eligible', 'dateOfExpiration': '2025-04-04', 'licenseNumber': 'B0608337260', + 'adverseActions': [], } ] self.assertEqual(expected_licenses, provider_data['licenses']) diff --git a/backend/social-work-app/stacks/api_stack/v1_api/api_model.py b/backend/social-work-app/stacks/api_stack/v1_api/api_model.py index cf8e150eb3..576d0d86ed 100644 --- a/backend/social-work-app/stacks/api_stack/v1_api/api_model.py +++ b/backend/social-work-app/stacks/api_stack/v1_api/api_model.py @@ -1416,6 +1416,59 @@ def _public_provider_detailed_response_schema(self): }, ) + @property + def _public_adverse_action_schema(self): + """Sanitized adverse action items for public responses; mirrors AdverseActionPublicResponseSchema. + + Unlike the staff-facing `_adverse_action_schema`, this omits `encumbranceType`, + `clinicalPrivilegeActionCategories`, and `liftingUser`/`submittingUser` so that no NPDB category or + staff-only information is exposed to the public. + """ + return JsonSchema( + type=JsonSchemaType.ARRAY, + items=JsonSchema( + type=JsonSchemaType.OBJECT, + required=[ + 'type', + 'compact', + 'providerId', + 'jurisdiction', + 'licenseTypeAbbreviation', + 'licenseType', + 'licenseScope', + 'actionAgainst', + 'effectiveStartDate', + 'creationDate', + 'adverseActionId', + 'dateOfUpdate', + ], + properties={ + 'type': JsonSchema(type=JsonSchemaType.STRING, enum=['adverseAction']), + 'compact': JsonSchema(type=JsonSchemaType.STRING, enum=self.stack.node.get_context('compacts')), + 'providerId': JsonSchema(type=JsonSchemaType.STRING, pattern=compact_connect_api.UUID4_FORMAT), + 'jurisdiction': JsonSchema( + type=JsonSchemaType.STRING, + enum=self.stack.node.get_context('jurisdictions'), + ), + 'licenseTypeAbbreviation': JsonSchema(type=JsonSchemaType.STRING), + 'licenseType': JsonSchema(type=JsonSchemaType.STRING), + 'licenseScope': JsonSchema(type=JsonSchemaType.STRING, enum=['single-state', 'multi-state']), + 'actionAgainst': JsonSchema(type=JsonSchemaType.STRING), + 'effectiveStartDate': JsonSchema( + type=JsonSchemaType.STRING, format='date', pattern=compact_connect_api.YMD_FORMAT + ), + 'creationDate': JsonSchema( + type=JsonSchemaType.STRING, format='date', pattern=compact_connect_api.YMD_FORMAT + ), + 'adverseActionId': JsonSchema(type=JsonSchemaType.STRING), + 'effectiveLiftDate': JsonSchema( + type=JsonSchemaType.STRING, format='date', pattern=compact_connect_api.YMD_FORMAT + ), + 'dateOfUpdate': JsonSchema(type=JsonSchemaType.STRING, format='date-time'), + }, + ), + ) + @property def _public_license_public_response_schema(self): """License items in public GET provider responses; mirrors LicensePublicResponseSchema.""" @@ -1448,6 +1501,7 @@ def _public_license_public_response_schema(self): type=JsonSchemaType.STRING, format='date', pattern=compact_connect_api.YMD_FORMAT ), 'licenseNumber': JsonSchema(type=JsonSchemaType.STRING, min_length=1, max_length=100), + 'adverseActions': self._public_adverse_action_schema, }, ) @@ -1484,6 +1538,7 @@ def _public_privilege_response_schema(self): 'dateOfExpiration': JsonSchema( type=JsonSchemaType.STRING, format='date', pattern=compact_connect_api.YMD_FORMAT ), + 'adverseActions': self._public_adverse_action_schema, 'administratorSetStatus': JsonSchema(type=JsonSchemaType.STRING, enum=['active', 'inactive']), 'status': JsonSchema(type=JsonSchemaType.STRING, enum=['active', 'inactive']), }, diff --git a/backend/social-work-app/tests/resources/snapshots/PUBLIC_GET_PROVIDER_RESPONSE_SCHEMA.json b/backend/social-work-app/tests/resources/snapshots/PUBLIC_GET_PROVIDER_RESPONSE_SCHEMA.json index ba7b7e05ad..a6367effd3 100644 --- a/backend/social-work-app/tests/resources/snapshots/PUBLIC_GET_PROVIDER_RESPONSE_SCHEMA.json +++ b/backend/social-work-app/tests/resources/snapshots/PUBLIC_GET_PROVIDER_RESPONSE_SCHEMA.json @@ -113,6 +113,141 @@ "maxLength": 100, "minLength": 1, "type": "string" + }, + "adverseActions": { + "items": { + "properties": { + "type": { + "enum": [ + "adverseAction" + ], + "type": "string" + }, + "compact": { + "enum": [ + "socw" + ], + "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": { + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "gu", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "mp", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" + ], + "type": "string" + }, + "licenseTypeAbbreviation": { + "type": "string" + }, + "licenseType": { + "type": "string" + }, + "licenseScope": { + "enum": [ + "single-state", + "multi-state" + ], + "type": "string" + }, + "actionAgainst": { + "type": "string" + }, + "effectiveStartDate": { + "format": "date", + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string" + }, + "creationDate": { + "format": "date", + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string" + }, + "adverseActionId": { + "type": "string" + }, + "effectiveLiftDate": { + "format": "date", + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string" + }, + "dateOfUpdate": { + "format": "date-time", + "type": "string" + } + }, + "required": [ + "type", + "compact", + "providerId", + "jurisdiction", + "licenseTypeAbbreviation", + "licenseType", + "licenseScope", + "actionAgainst", + "effectiveStartDate", + "creationDate", + "adverseActionId", + "dateOfUpdate" + ], + "type": "object" + }, + "type": "array" } }, "required": [ @@ -280,6 +415,141 @@ "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string" }, + "adverseActions": { + "items": { + "properties": { + "type": { + "enum": [ + "adverseAction" + ], + "type": "string" + }, + "compact": { + "enum": [ + "socw" + ], + "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": { + "enum": [ + "al", + "ak", + "az", + "ar", + "ca", + "co", + "ct", + "de", + "dc", + "fl", + "ga", + "gu", + "hi", + "id", + "il", + "in", + "ia", + "ks", + "ky", + "la", + "me", + "md", + "ma", + "mi", + "mn", + "ms", + "mo", + "mt", + "mp", + "ne", + "nv", + "nh", + "nj", + "nm", + "ny", + "nc", + "nd", + "oh", + "ok", + "or", + "pa", + "ri", + "sc", + "sd", + "tn", + "tx", + "ut", + "vt", + "va", + "vi", + "wa", + "wv", + "wi", + "wy" + ], + "type": "string" + }, + "licenseTypeAbbreviation": { + "type": "string" + }, + "licenseType": { + "type": "string" + }, + "licenseScope": { + "enum": [ + "single-state", + "multi-state" + ], + "type": "string" + }, + "actionAgainst": { + "type": "string" + }, + "effectiveStartDate": { + "format": "date", + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string" + }, + "creationDate": { + "format": "date", + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string" + }, + "adverseActionId": { + "type": "string" + }, + "effectiveLiftDate": { + "format": "date", + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string" + }, + "dateOfUpdate": { + "format": "date-time", + "type": "string" + } + }, + "required": [ + "type", + "compact", + "providerId", + "jurisdiction", + "licenseTypeAbbreviation", + "licenseType", + "licenseScope", + "actionAgainst", + "effectiveStartDate", + "creationDate", + "adverseActionId", + "dateOfUpdate" + ], + "type": "object" + }, + "type": "array" + }, "administratorSetStatus": { "enum": [ "active", From e0f83ccbdffe1d06cd924c5de2717aaf6bf00b75 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Tue, 28 Jul 2026 14:55:12 -0500 Subject: [PATCH 07/14] update node postcss dependency to address vulnerability --- .../lambdas/nodejs/package.json | 2 +- .../compact-connect/lambdas/nodejs/yarn.lock | 18 +++++++++--------- .../lambdas/nodejs/package.json | 2 +- .../cosmetology-app/lambdas/nodejs/yarn.lock | 18 +++++++++--------- .../lambdas/nodejs/package.json | 2 +- .../social-work-app/lambdas/nodejs/yarn.lock | 18 +++++++++--------- 6 files changed, 30 insertions(+), 30 deletions(-) diff --git a/backend/compact-connect/lambdas/nodejs/package.json b/backend/compact-connect/lambdas/nodejs/package.json index 1dbbcaeb4d..7d007fc78b 100644 --- a/backend/compact-connect/lambdas/nodejs/package.json +++ b/backend/compact-connect/lambdas/nodejs/package.json @@ -5,7 +5,7 @@ "description": "NodeJS lambdas for CompactConnect", "resolutions": { "fast-xml-parser": "5.7.3", - "postcss": "8.5.12" + "postcss": "8.5.24" }, "scripts": { "build": "tsc", diff --git a/backend/compact-connect/lambdas/nodejs/yarn.lock b/backend/compact-connect/lambdas/nodejs/yarn.lock index 07e6fd8dd6..e6daa62f01 100644 --- a/backend/compact-connect/lambdas/nodejs/yarn.lock +++ b/backend/compact-connect/lambdas/nodejs/yarn.lock @@ -4656,10 +4656,10 @@ ms@^2.1.1, ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -nanoid@^3.3.11: - version "3.3.11" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" - integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== +nanoid@^3.3.16: + version "3.3.16" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.16.tgz#a04d8ec4b1f10009d2d533947aefe4293737816c" + integrity sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q== napi-postinstall@^0.3.4: version "0.3.4" @@ -4870,12 +4870,12 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" -postcss@8.5.12, postcss@^8.3.11: - version "8.5.12" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.12.tgz#cd0c0f667f7cb0521e2313234ea6e707a9ec1ddb" - integrity sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA== +postcss@8.5.24, postcss@^8.3.11: + version "8.5.24" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.24.tgz#01d8b032451e1b9ec41ae66eaf02843f42a720d2" + integrity sha512-8RyVklq0owXUTa4xlpzu4l9AaVKIdQvAcOHZWaMh98HgySsUtxRVf/chRe3dsSLqb6i40BzGRzEUddRaI+9TSw== dependencies: - nanoid "^3.3.11" + nanoid "^3.3.16" picocolors "^1.1.1" source-map-js "^1.2.1" diff --git a/backend/cosmetology-app/lambdas/nodejs/package.json b/backend/cosmetology-app/lambdas/nodejs/package.json index 1dbbcaeb4d..7d007fc78b 100644 --- a/backend/cosmetology-app/lambdas/nodejs/package.json +++ b/backend/cosmetology-app/lambdas/nodejs/package.json @@ -5,7 +5,7 @@ "description": "NodeJS lambdas for CompactConnect", "resolutions": { "fast-xml-parser": "5.7.3", - "postcss": "8.5.12" + "postcss": "8.5.24" }, "scripts": { "build": "tsc", diff --git a/backend/cosmetology-app/lambdas/nodejs/yarn.lock b/backend/cosmetology-app/lambdas/nodejs/yarn.lock index 07e6fd8dd6..e6daa62f01 100644 --- a/backend/cosmetology-app/lambdas/nodejs/yarn.lock +++ b/backend/cosmetology-app/lambdas/nodejs/yarn.lock @@ -4656,10 +4656,10 @@ ms@^2.1.1, ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -nanoid@^3.3.11: - version "3.3.11" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" - integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== +nanoid@^3.3.16: + version "3.3.16" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.16.tgz#a04d8ec4b1f10009d2d533947aefe4293737816c" + integrity sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q== napi-postinstall@^0.3.4: version "0.3.4" @@ -4870,12 +4870,12 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" -postcss@8.5.12, postcss@^8.3.11: - version "8.5.12" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.12.tgz#cd0c0f667f7cb0521e2313234ea6e707a9ec1ddb" - integrity sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA== +postcss@8.5.24, postcss@^8.3.11: + version "8.5.24" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.24.tgz#01d8b032451e1b9ec41ae66eaf02843f42a720d2" + integrity sha512-8RyVklq0owXUTa4xlpzu4l9AaVKIdQvAcOHZWaMh98HgySsUtxRVf/chRe3dsSLqb6i40BzGRzEUddRaI+9TSw== dependencies: - nanoid "^3.3.11" + nanoid "^3.3.16" picocolors "^1.1.1" source-map-js "^1.2.1" diff --git a/backend/social-work-app/lambdas/nodejs/package.json b/backend/social-work-app/lambdas/nodejs/package.json index 1dbbcaeb4d..7d007fc78b 100644 --- a/backend/social-work-app/lambdas/nodejs/package.json +++ b/backend/social-work-app/lambdas/nodejs/package.json @@ -5,7 +5,7 @@ "description": "NodeJS lambdas for CompactConnect", "resolutions": { "fast-xml-parser": "5.7.3", - "postcss": "8.5.12" + "postcss": "8.5.24" }, "scripts": { "build": "tsc", diff --git a/backend/social-work-app/lambdas/nodejs/yarn.lock b/backend/social-work-app/lambdas/nodejs/yarn.lock index 07e6fd8dd6..e6daa62f01 100644 --- a/backend/social-work-app/lambdas/nodejs/yarn.lock +++ b/backend/social-work-app/lambdas/nodejs/yarn.lock @@ -4656,10 +4656,10 @@ ms@^2.1.1, ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -nanoid@^3.3.11: - version "3.3.11" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" - integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== +nanoid@^3.3.16: + version "3.3.16" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.16.tgz#a04d8ec4b1f10009d2d533947aefe4293737816c" + integrity sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q== napi-postinstall@^0.3.4: version "0.3.4" @@ -4870,12 +4870,12 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" -postcss@8.5.12, postcss@^8.3.11: - version "8.5.12" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.12.tgz#cd0c0f667f7cb0521e2313234ea6e707a9ec1ddb" - integrity sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA== +postcss@8.5.24, postcss@^8.3.11: + version "8.5.24" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.24.tgz#01d8b032451e1b9ec41ae66eaf02843f42a720d2" + integrity sha512-8RyVklq0owXUTa4xlpzu4l9AaVKIdQvAcOHZWaMh98HgySsUtxRVf/chRe3dsSLqb6i40BzGRzEUddRaI+9TSw== dependencies: - nanoid "^3.3.11" + nanoid "^3.3.16" picocolors "^1.1.1" source-map-js "^1.2.1" From 01587b2fd6ac91c1ca4e739ddd4170819d1260a5 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Tue, 28 Jul 2026 17:21:49 -0500 Subject: [PATCH 08/14] add cleanup step to encumbrance smoke tests --- backend/social-work-app/tests/smoke/encumbrance_smoke_tests.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/social-work-app/tests/smoke/encumbrance_smoke_tests.py b/backend/social-work-app/tests/smoke/encumbrance_smoke_tests.py index c0be35fec3..2e8b6f00ad 100644 --- a/backend/social-work-app/tests/smoke/encumbrance_smoke_tests.py +++ b/backend/social-work-app/tests/smoke/encumbrance_smoke_tests.py @@ -798,6 +798,9 @@ def run_encumbrance_smoke_tests(): provider_id = config.test_provider_id + # Delete leftover adverse actions before loading provider records. + clean_adverse_actions() + # Get jurisdiction information from privilege # Query database directly for privilege records provider_user_records = get_provider_user_records(ENCUMBRANCE_SMOKE_COMPACT, provider_id) From 486c8778bef826edb58e0a944c43cc57f692af1f Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Wed, 29 Jul 2026 10:18:24 -0500 Subject: [PATCH 09/14] Set DEBUG to false and remove PII/sensitive values from non-debug logs --- backend/common-cdk/common_constructs/stack.py | 4 ++- .../lambdas/nodejs/cognito-emails/lambda.ts | 2 +- .../nodejs/lib/email/base-email-service.ts | 4 +-- .../lib/email/ingest-event-email-service.ts | 6 ++-- .../cc_common/data_model/data_client.py | 10 +++++-- .../cc_common/data_model/user_client.py | 6 ++-- .../python/common/cc_common/signature_auth.py | 10 +++++-- .../lambdas/python/common/cc_common/utils.py | 9 ++++-- .../provider-data-v1/handlers/bulk_upload.py | 5 +++- .../provider-data-v1/handlers/privileges.py | 7 +++-- .../provider-data-v1/handlers/registration.py | 30 ++++++++++++------- .../handlers/populate_provider_documents.py | 8 ++++- .../python/staff-user-pre-token/main.py | 6 +++- .../stacks/persistent_stack/staff_users.py | 2 +- .../lambdas/nodejs/cognito-emails/lambda.ts | 2 +- .../nodejs/lib/email/base-email-service.ts | 14 +++++++++ .../lib/email/ingest-event-email-service.ts | 6 ++-- .../cc_common/data_model/data_client.py | 8 +++-- .../cc_common/data_model/user_client.py | 6 ++-- .../python/common/cc_common/signature_auth.py | 10 +++++-- .../lambdas/python/common/cc_common/utils.py | 9 ++++-- .../provider-data-v1/handlers/bulk_upload.py | 5 +++- .../handlers/populate_provider_documents.py | 8 ++++- .../python/staff-user-pre-token/main.py | 6 +++- .../stacks/persistent_stack/staff_users.py | 2 +- .../lambdas/nodejs/cognito-emails/lambda.ts | 2 +- .../nodejs/lib/email/base-email-service.ts | 14 +++++++++ .../lib/email/ingest-event-email-service.ts | 6 ++-- .../cc_common/data_model/data_client.py | 8 +++-- .../cc_common/data_model/user_client.py | 6 ++-- .../python/common/cc_common/signature_auth.py | 10 +++++-- .../lambdas/python/common/cc_common/utils.py | 9 ++++-- .../handlers/populate_provider_documents.py | 8 ++++- .../python/staff-user-pre-token/main.py | 6 +++- .../stacks/persistent_stack/staff_users.py | 2 +- 35 files changed, 186 insertions(+), 70 deletions(-) diff --git a/backend/common-cdk/common_constructs/stack.py b/backend/common-cdk/common_constructs/stack.py index c1422d0371..44f9f66de8 100644 --- a/backend/common-cdk/common_constructs/stack.py +++ b/backend/common-cdk/common_constructs/stack.py @@ -80,7 +80,9 @@ def license_types(self): @cached_property def common_env_vars(self): return { - 'DEBUG': 'true', + # DEBUG-level logging can include sensitive request/response data, so it must + # not be enabled by default. It remains available to aid technical support teams. + 'DEBUG': 'false', 'ALLOWED_ORIGINS': json.dumps(self.allowed_origins), 'COMPACTS': json.dumps(self.node.get_context('compacts')), 'JURISDICTIONS': json.dumps(self.node.get_context('jurisdictions')), diff --git a/backend/compact-connect/lambdas/nodejs/cognito-emails/lambda.ts b/backend/compact-connect/lambdas/nodejs/cognito-emails/lambda.ts index 5660ac1016..dcb305dca9 100644 --- a/backend/compact-connect/lambdas/nodejs/cognito-emails/lambda.ts +++ b/backend/compact-connect/lambdas/nodejs/cognito-emails/lambda.ts @@ -85,7 +85,7 @@ export class Lambda implements LambdaInterface { logger.info('Processing Cognito custom message event', { triggerSource: event.triggerSource, userPoolId: event.userPoolId, - userName: event.userName + userName: this.emailService.maskEmail(event.userName) }); try { diff --git a/backend/compact-connect/lambdas/nodejs/lib/email/base-email-service.ts b/backend/compact-connect/lambdas/nodejs/lib/email/base-email-service.ts index 14a5938b08..4b6dc5f08e 100644 --- a/backend/compact-connect/lambdas/nodejs/lib/email/base-email-service.ts +++ b/backend/compact-connect/lambdas/nodejs/lib/email/base-email-service.ts @@ -67,7 +67,7 @@ export abstract class BaseEmailService { return `${environmentVariableService.getUiBasePathUrl()}/img/email`; } - protected maskEmail(email: string): string { + public maskEmail(email: string): string { const at = email.indexOf('@'); if (at <= 0) { @@ -77,7 +77,7 @@ export abstract class BaseEmailService { return `${email[0]}***${email.slice(at)}`; } - protected maskEmails(emails: string[]): string[] { + public maskEmails(emails: string[]): string[] { return emails.map((email) => this.maskEmail(email)); } diff --git a/backend/compact-connect/lambdas/nodejs/lib/email/ingest-event-email-service.ts b/backend/compact-connect/lambdas/nodejs/lib/email/ingest-event-email-service.ts index 839e3c07ba..3a74cc4d45 100644 --- a/backend/compact-connect/lambdas/nodejs/lib/email/ingest-event-email-service.ts +++ b/backend/compact-connect/lambdas/nodejs/lib/email/ingest-event-email-service.ts @@ -17,7 +17,7 @@ export class IngestEventEmailService extends BaseEmailService { jurisdiction: string, recipients: string[] ) { - this.logger.info('Sending report email', { recipients: recipients }); + this.logger.info('Sending report email', { recipients: this.maskEmails(recipients) }); // Generate the HTML report const htmlContent = this.generateReport(events, compactName, jurisdiction); @@ -31,7 +31,7 @@ export class IngestEventEmailService extends BaseEmailService { } public async sendAllsWellEmail(compactName: string, jurisdiction: string, recipients: string[]) { - this.logger.info('Sending alls well email', { recipients: recipients }); + this.logger.info('Sending alls well email', { recipients: this.maskEmails(recipients) }); // Generate the HTML report const report = this.getNewEmailTemplate(); @@ -52,7 +52,7 @@ export class IngestEventEmailService extends BaseEmailService { } public async sendNoLicenseUpdatesEmail(compactName: string, jurisdiction: string, recipients: string[]) { - this.logger.info('Sending no license updates email', { recipients: recipients }); + this.logger.info('Sending no license updates email', { recipients: this.maskEmails(recipients) }); // Generate the HTML report const report = this.getNewEmailTemplate(); 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 e8773f4416..ea06090395 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 @@ -122,7 +122,7 @@ def get_ssn_by_provider_id(self, *, compact: str, provider_id: str) -> str: raise CCInternalException(f'Expected 1 SSN index record, got {len(resp)}') return resp[0]['ssn'] - @logger_inject_kwargs(logger, 'compact', 'jurisdiction', 'family_name', 'given_name') + @logger_inject_kwargs(logger, 'compact', 'jurisdiction') def find_matching_license_record( self, *, @@ -146,6 +146,8 @@ def find_matching_license_record( :return: The matching license record if found, None otherwise """ logger.info('Querying license records', compact=compact, state=jurisdiction) + # family_name/given_name are PII, so they are only logged at DEBUG level + logger.debug('Querying license records details', family_name=family_name, given_name=given_name) resp = self.config.provider_table.query( IndexName=self.config.license_gsi_name, @@ -243,7 +245,7 @@ def get_provider_user_records( return ProviderUserRecords(resp['Items']) @paginated_query(set_query_limit_to_match_page_size=False) - @logger_inject_kwargs(logger, 'compact', 'provider_name', 'jurisdiction') + @logger_inject_kwargs(logger, 'compact', 'jurisdiction') def get_providers_sorted_by_family_name( self, *, @@ -255,6 +257,8 @@ def get_providers_sorted_by_family_name( exclude_providers_without_privileges: bool = False, ): logger.info('Getting providers by family name') + # provider_name is PII, so it is only logged at DEBUG level + logger.debug('Getting providers by family name details', provider_name=provider_name) # Create a name value to use in key condition if name fields are provided name_value = None @@ -4372,7 +4376,7 @@ def clear_provider_email_verification_data( logger.error('Failed to clear provider email verification data', error=str(e)) raise CCAwsServiceException('Failed to clear provider email verification data') from e - @logger_inject_kwargs(logger, 'compact', 'provider_id', 'new_email_address') + @logger_inject_kwargs(logger, 'compact', 'provider_id') def complete_provider_email_update( self, *, diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/user_client.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/user_client.py index fcbbfceab3..eea8686687 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/user_client.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/user_client.py @@ -302,7 +302,9 @@ def create_user(self, compact: str, attributes: dict, permissions: dict): :param dict permissions: The permissions for the user :return: """ - logger.info('Creating staff user', attributes=attributes) + logger.info('Creating staff user', compact=compact) + # attributes contains PII (email, given/family name), so it is only logged at DEBUG level + logger.debug('Creating staff user with attributes', attributes=attributes) attributes = self.user_attributes_schema.load(attributes) permissions = self.compact_permissions_schema.load(permissions) @@ -329,7 +331,7 @@ def create_user(self, compact: str, attributes: dict, permissions: dict): # If the user was previously disabled, re-enable them if not resp.get('Enabled', True): - logger.info('Re-enabling previously disabled user', user_id=user_id, email=attributes['email']) + logger.info('Re-enabling previously disabled user', user_id=user_id) self.config.cognito_client.admin_enable_user( UserPoolId=self.config.user_pool_id, Username=attributes['email'] ) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/signature_auth.py b/backend/compact-connect/lambdas/python/common/cc_common/signature_auth.py index be05eaf256..8b5e3e79ce 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/signature_auth.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/signature_auth.py @@ -186,7 +186,9 @@ def _validate_nonce_format(nonce: str) -> None: import re if not re.match(r'^[a-zA-Z0-9-]+$', nonce): - logger.warning('Invalid nonce format - contains invalid characters', nonce=nonce) + logger.warning('Invalid nonce format - contains invalid characters') + # the nonce is part of the request signing scheme, so it is only logged at DEBUG level + logger.debug('Invalid nonce format details', nonce=nonce) raise CCUnauthorizedCustomResponseException('Nonce can only contain alphanumeric characters and hyphens') @@ -217,11 +219,12 @@ def _validate_signature(event: dict, compact: str, jurisdiction: str, public_key # Validate all required headers are present if not all([algorithm, timestamp_str, nonce, signature_b64, key_id]): + # nonce is part of the request signing scheme, so it is not logged here; presence is sufficient for triage logger.warning( 'Missing required signature headers', algorithm=algorithm, timestamp=timestamp_str, - nonce=nonce, + nonce_present=bool(nonce), signature_present=bool(signature_b64), key_id=key_id, compact=compact, @@ -380,8 +383,9 @@ def _validate_and_store_nonce(compact: str, jurisdiction: str, nonce: str) -> No 'Nonce reuse detected', compact=compact, jurisdiction=jurisdiction, - nonce=nonce, ) + # the nonce is part of the request signing scheme, so it is only logged at DEBUG level + logger.debug('Nonce reuse detected details', nonce=nonce) raise CCUnauthorizedCustomResponseException('Nonce has already been used') from e logger.error('Failed to validate nonce', error=str(e), compact=compact, jurisdiction=jurisdiction) raise CCUnauthorizedException('Failed to validate nonce') from e 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 516d5704dd..5b408beb7e 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/utils.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/utils.py @@ -113,15 +113,18 @@ def caught_handler(event, context: LambdaContext): content_type = event['headers'].get('Content-Type') - # Propagate these keys to all log messages in this with block + # Propagate these keys to all log messages in this with block. + # The caller's Cognito username (their email address) is intentionally excluded here, since the + # 'sub' claim already provides a non-PII identifier that is sufficient for tracing requests to a user. + # Similarly, only query parameter *names* (not values) are logged, since query parameter values could + # contain PII or other sensitive data depending on the endpoint. with logger.append_context_keys( method=event['httpMethod'], origin=origin, path=event['requestContext']['resourcePath'], content_type=content_type, identity={'user': event['requestContext'].get('authorizer', {}).get('claims', {}).get('sub')}, - query_params=event['queryStringParameters'], - username=event['requestContext'].get('authorizer', {}).get('claims', {}).get('cognito:username'), + query_param_keys=sorted((event.get('queryStringParameters') or {}).keys()), ): logger.info('Incoming request') 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 f28b6a24a8..59e959066e 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 @@ -209,9 +209,12 @@ def process_bulk_upload_file( 'Invalid license in line %s uploaded: %s', i + 1, str(e), - valid_data=report_license_data, + compact=compact, + jurisdiction=jurisdiction, exc_info=e, ) + # valid_data may contain licensee PII (name, license number, npi), so it is only logged at DEBUG + logger.debug('Invalid license record details', record_number=i + 1, valid_data=report_license_data) event_writer.put_event( Entry={ 'Source': f'org.compactconnect.bulk-ingest.{object_key}', diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/privileges.py b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/privileges.py index 641bfe9d52..e341475d47 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/privileges.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/privileges.py @@ -87,7 +87,6 @@ def deactivate_privilege(event: dict, context: LambdaContext): # noqa: ARG001 u logger.info( 'Sending privilege deactivation notification to provider', provider_id=provider_id, - provider_email=provider_email, ) config.email_service_client.send_provider_privilege_deactivation_email( compact=compact, @@ -138,8 +137,10 @@ def privilege_purchase_message_handler(message: dict): privileges = message['detail']['privileges'] provider_email = message['detail']['providerEmail'] transaction_date_time = message['detail']['eventTime'] + # All privileges in a purchase belong to the same provider, so any entry's providerId identifies the provider + provider_id = privileges[0].get('providerId') if privileges else None - with logger.append_context_keys(provider_email=provider_email): + with logger.append_context_keys(provider_id=provider_id): logger.info('Processing privilege purchase notification') error_messages = [] @@ -147,7 +148,7 @@ def privilege_purchase_message_handler(message: dict): # Send notification to the jurisdiction try: transaction_date = datetime.fromisoformat(transaction_date_time) - logger.info('Sending privilege purchase notification to provider', provider=provider_email) + logger.info('Sending privilege purchase notification to provider') config.email_service_client.send_privilege_purchase_email( transaction_date=transaction_date.date().isoformat(), provider_email=provider_email, diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/registration.py b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/registration.py index e25d8874ea..fa0d6f7402 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/registration.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/registration.py @@ -92,17 +92,18 @@ def _resend_invitation_and_complete(email: str) -> dict: return {'message': 'request processed'} -def _cleanup_old_registration(old_email: str, cognito_user: dict) -> None: +def _cleanup_old_registration(old_email: str, cognito_user: dict, provider_id: str) -> None: """Delete old Cognito user to allow new registration. :param old_email: Email of the old registration to clean up :param cognito_user: Cognito user data for logging + :param provider_id: The provider ID associated with the old registration, for non-PII log traceability """ try: logger.info( 'User never completed registration flow for previous email and has provided new email for registration, ' 'deleting old Cognito user associated with previous email.', - previous_email=old_email, + provider_id=provider_id, user_create_date=cognito_user['UserCreateDate'].isoformat(), user_last_modified_date=cognito_user['UserLastModifiedDate'].isoformat(), user_status=cognito_user['UserStatus'], @@ -110,7 +111,7 @@ def _cleanup_old_registration(old_email: str, cognito_user: dict) -> None: config.cognito_client.admin_delete_user(UserPoolId=config.provider_user_pool_id, Username=old_email) except ClientError as delete_e: logger.error( - 'Failed to delete old Cognito user during re-registration', error=str(delete_e), old_email=old_email + 'Failed to delete old Cognito user during re-registration', error=str(delete_e), provider_id=provider_id ) # Continue with registration anyway @@ -155,27 +156,32 @@ def register_provider(event: dict, context: LambdaContext): # noqa: ARG001 unus 'Rate limit exceeded for ip address', compact=body['compact'], jurisdiction=body['jurisdiction'], - given_name=body['givenName'], - family_name=body['familyName'], license_type=body['licenseType'], ip_address=source_ip, ) + # given/family name are PII, so they are only logged at DEBUG level + logger.debug( + 'Rate-limited registration attempt details', given_name=body['givenName'], family_name=body['familyName'] + ) metrics.add_metric(name='registration-rate-limit-throttles', unit=MetricUnit.Count, value=1) metrics.add_metric(name=REGISTRATION_ATTEMPT_METRIC_NAME, unit=MetricUnit.NoUnit, value=0) raise CCRateLimitingException('Rate limit exceeded. Please try again later.') # Verify reCAPTCHA token if not verify_recaptcha(body['token']): + # NOTE: the reCAPTCHA token itself is never logged, since it is sensitive authentication data. logger.info( 'Invalid reCAPTCHA token', - token=body['token'], compact=body['compact'], jurisdiction=body['jurisdiction'], - given_name=body['givenName'], - family_name=body['familyName'], license_type=body['licenseType'], ip_address=source_ip, ) + logger.debug( + 'Invalid reCAPTCHA registration attempt details', + given_name=body['givenName'], + family_name=body['familyName'], + ) metrics.add_metric(name=RECAPTCHA_ATTEMPT_METRIC_NAME, unit=MetricUnit.NoUnit, value=0) metrics.add_metric(name=REGISTRATION_ATTEMPT_METRIC_NAME, unit=MetricUnit.NoUnit, value=0) raise CCAccessDeniedException('Invalid request') @@ -269,9 +275,13 @@ def register_provider(event: dict, context: LambdaContext): # noqa: ARG001 unus 'No matching license record found for request', compact=body['compact'], jurisdiction=body['jurisdiction'], + license_type=body['licenseType'], + ) + # given/family name are PII, so they are only logged at DEBUG level + logger.debug( + 'No matching license record found for request details', given_name=body['givenName'], family_name=body['familyName'], - license_type=body['licenseType'], ) metrics.add_metric(name=REGISTRATION_ATTEMPT_METRIC_NAME, unit=MetricUnit.NoUnit, value=0) return {'message': 'request processed'} @@ -297,7 +307,7 @@ def register_provider(event: dict, context: LambdaContext): # noqa: ARG001 unus return _resend_invitation_and_complete(body['email']) # Different email: cleanup account and then proceed with registration for provided email - _cleanup_old_registration(registered_email, cognito_user) + _cleanup_old_registration(registered_email, cognito_user, matching_record.providerId) else: logger.warning( 'User attempted to register for account with existing registered email.', diff --git a/backend/compact-connect/lambdas/python/search/handlers/populate_provider_documents.py b/backend/compact-connect/lambdas/python/search/handlers/populate_provider_documents.py index 855016fec4..196dd3c6a9 100644 --- a/backend/compact-connect/lambdas/python/search/handlers/populate_provider_documents.py +++ b/backend/compact-connect/lambdas/python/search/handlers/populate_provider_documents.py @@ -209,7 +209,13 @@ def populate_provider_documents(event: dict, context: LambdaContext): provider_id = provider_record.get('providerId') if not provider_id: - logger.warning('Provider record missing providerId', record=provider_record) + logger.warning( + 'Provider record missing providerId', + pk=provider_record.get('pk'), + sk=provider_record.get('sk'), + ) + # the record may contain PII (name, DOB, etc.), so it is only logged at DEBUG level + logger.debug('Provider record missing providerId details', record=provider_record) compact_stats['providers_failed'] += 1 continue diff --git a/backend/compact-connect/lambdas/python/staff-user-pre-token/main.py b/backend/compact-connect/lambdas/python/staff-user-pre-token/main.py index 07fa0ff5c1..8908b12ef6 100644 --- a/backend/compact-connect/lambdas/python/staff-user-pre-token/main.py +++ b/backend/compact-connect/lambdas/python/staff-user-pre-token/main.py @@ -14,7 +14,9 @@ @logger.inject_lambda_context() def customize_scopes(event: dict, context: LambdaContext): # noqa: ARG001 unused-argument """Customize the scopes in the access token before AWS generates and issues it""" - logger.info('Received event', event=event) + # The full event includes PII from Cognito user attributes (email, given/family name), so it is only + # logged at DEBUG level. + logger.debug('Received event', event=event) try: sub = event['request']['userAttributes']['sub'] @@ -26,6 +28,8 @@ def customize_scopes(event: dict, context: LambdaContext): # noqa: ARG001 unuse event['response']['claimsAndScopeOverrideDetails'] = None return event + logger.info('Customizing scopes for user', sub=sub) + try: user_data = UserData(sub) logger.debug('Adding scopes', scopes=user_data.scopes) diff --git a/backend/compact-connect/stacks/persistent_stack/staff_users.py b/backend/compact-connect/stacks/persistent_stack/staff_users.py index c1e8acf41b..5ce6a0d4dc 100644 --- a/backend/compact-connect/stacks/persistent_stack/staff_users.py +++ b/backend/compact-connect/stacks/persistent_stack/staff_users.py @@ -132,10 +132,10 @@ def _add_scope_customization(self, stack: ps.PersistentStack): handler='customize_scopes', alarm_topic=stack.alarm_topic, environment={ - 'DEBUG': 'true', 'USERS_TABLE_NAME': self.user_table.table_name, 'COMPACTS': json.dumps(compacts), 'JURISDICTIONS': json.dumps(jurisdictions), + # spread last so environment-appropriate values (e.g. DEBUG) are not clobbered by the above **stack.common_env_vars, }, ) diff --git a/backend/cosmetology-app/lambdas/nodejs/cognito-emails/lambda.ts b/backend/cosmetology-app/lambdas/nodejs/cognito-emails/lambda.ts index dda0a42946..5e799cdcc1 100644 --- a/backend/cosmetology-app/lambdas/nodejs/cognito-emails/lambda.ts +++ b/backend/cosmetology-app/lambdas/nodejs/cognito-emails/lambda.ts @@ -82,7 +82,7 @@ export class Lambda implements LambdaInterface { logger.info('Processing Cognito custom message event', { triggerSource: event.triggerSource, userPoolId: event.userPoolId, - userName: event.userName + userName: this.emailService.maskEmail(event.userName) }); try { diff --git a/backend/cosmetology-app/lambdas/nodejs/lib/email/base-email-service.ts b/backend/cosmetology-app/lambdas/nodejs/lib/email/base-email-service.ts index 7f7689658c..5454c9c594 100644 --- a/backend/cosmetology-app/lambdas/nodejs/lib/email/base-email-service.ts +++ b/backend/cosmetology-app/lambdas/nodejs/lib/email/base-email-service.ts @@ -64,6 +64,20 @@ export abstract class BaseEmailService { return `${environmentVariableService.getUiBasePathUrl()}/img/email`; } + public maskEmail(email: string): string { + const at = email.indexOf('@'); + + if (at <= 0) { + return '***'; + } + + return `${email[0]}***${email.slice(at)}`; + } + + public maskEmails(emails: string[]): string[] { + return emails.map((email) => this.maskEmail(email)); + } + protected async sendEmail({ htmlContent, subject, recipients, errorMessage }: {htmlContent: string, subject: string, recipients: string[], errorMessage: string}) { try { diff --git a/backend/cosmetology-app/lambdas/nodejs/lib/email/ingest-event-email-service.ts b/backend/cosmetology-app/lambdas/nodejs/lib/email/ingest-event-email-service.ts index 839e3c07ba..3a74cc4d45 100644 --- a/backend/cosmetology-app/lambdas/nodejs/lib/email/ingest-event-email-service.ts +++ b/backend/cosmetology-app/lambdas/nodejs/lib/email/ingest-event-email-service.ts @@ -17,7 +17,7 @@ export class IngestEventEmailService extends BaseEmailService { jurisdiction: string, recipients: string[] ) { - this.logger.info('Sending report email', { recipients: recipients }); + this.logger.info('Sending report email', { recipients: this.maskEmails(recipients) }); // Generate the HTML report const htmlContent = this.generateReport(events, compactName, jurisdiction); @@ -31,7 +31,7 @@ export class IngestEventEmailService extends BaseEmailService { } public async sendAllsWellEmail(compactName: string, jurisdiction: string, recipients: string[]) { - this.logger.info('Sending alls well email', { recipients: recipients }); + this.logger.info('Sending alls well email', { recipients: this.maskEmails(recipients) }); // Generate the HTML report const report = this.getNewEmailTemplate(); @@ -52,7 +52,7 @@ export class IngestEventEmailService extends BaseEmailService { } public async sendNoLicenseUpdatesEmail(compactName: string, jurisdiction: string, recipients: string[]) { - this.logger.info('Sending no license updates email', { recipients: recipients }); + this.logger.info('Sending no license updates email', { recipients: this.maskEmails(recipients) }); // Generate the HTML report const report = this.getNewEmailTemplate(); diff --git a/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/data_client.py b/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/data_client.py index b9b0bde1e2..d961c74f8f 100644 --- a/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/data_client.py +++ b/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/data_client.py @@ -83,7 +83,7 @@ def get_ssn_by_provider_id(self, *, compact: str, provider_id: str) -> str: raise CCInternalException(f'Expected 1 SSN index record, got {len(resp)}') return resp[0]['ssn'] - @logger_inject_kwargs(logger, 'compact', 'jurisdiction', 'family_name', 'given_name') + @logger_inject_kwargs(logger, 'compact', 'jurisdiction') def find_matching_license_record( self, *, @@ -107,6 +107,8 @@ def find_matching_license_record( :return: The matching license record if found, None otherwise """ logger.info('Querying license records', compact=compact, state=jurisdiction) + # family_name/given_name are PII, so they are only logged at DEBUG level + logger.debug('Querying license records details', family_name=family_name, given_name=given_name) resp = self.config.provider_table.query( IndexName=self.config.license_gsi_name, @@ -204,7 +206,7 @@ def get_provider_user_records( return ProviderUserRecords(resp['Items']) @paginated_query(set_query_limit_to_match_page_size=False) - @logger_inject_kwargs(logger, 'compact', 'provider_name', 'jurisdiction') + @logger_inject_kwargs(logger, 'compact', 'jurisdiction') def get_providers_sorted_by_family_name( self, *, @@ -215,6 +217,8 @@ def get_providers_sorted_by_family_name( scan_forward: bool = True, ): logger.info('Getting providers by family name') + # provider_name is PII, so it is only logged at DEBUG level + logger.debug('Getting providers by family name details', provider_name=provider_name) # Create a name value to use in key condition if name fields are provided name_value = None diff --git a/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/user_client.py b/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/user_client.py index fcbbfceab3..eea8686687 100644 --- a/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/user_client.py +++ b/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/user_client.py @@ -302,7 +302,9 @@ def create_user(self, compact: str, attributes: dict, permissions: dict): :param dict permissions: The permissions for the user :return: """ - logger.info('Creating staff user', attributes=attributes) + logger.info('Creating staff user', compact=compact) + # attributes contains PII (email, given/family name), so it is only logged at DEBUG level + logger.debug('Creating staff user with attributes', attributes=attributes) attributes = self.user_attributes_schema.load(attributes) permissions = self.compact_permissions_schema.load(permissions) @@ -329,7 +331,7 @@ def create_user(self, compact: str, attributes: dict, permissions: dict): # If the user was previously disabled, re-enable them if not resp.get('Enabled', True): - logger.info('Re-enabling previously disabled user', user_id=user_id, email=attributes['email']) + logger.info('Re-enabling previously disabled user', user_id=user_id) self.config.cognito_client.admin_enable_user( UserPoolId=self.config.user_pool_id, Username=attributes['email'] ) diff --git a/backend/cosmetology-app/lambdas/python/common/cc_common/signature_auth.py b/backend/cosmetology-app/lambdas/python/common/cc_common/signature_auth.py index dce8c4dcdd..961962f981 100644 --- a/backend/cosmetology-app/lambdas/python/common/cc_common/signature_auth.py +++ b/backend/cosmetology-app/lambdas/python/common/cc_common/signature_auth.py @@ -185,7 +185,9 @@ def _validate_nonce_format(nonce: str) -> None: import re if not re.match(r'^[a-zA-Z0-9-]+$', nonce): - logger.warning('Invalid nonce format - contains invalid characters', nonce=nonce) + logger.warning('Invalid nonce format - contains invalid characters') + # the nonce is part of the request signing scheme, so it is only logged at DEBUG level + logger.debug('Invalid nonce format details', nonce=nonce) raise CCUnauthorizedCustomResponseException('Nonce can only contain alphanumeric characters and hyphens') @@ -216,11 +218,12 @@ def _validate_signature(event: dict, compact: str, jurisdiction: str, public_key # Validate all required headers are present if not all([algorithm, timestamp_str, nonce, signature_b64, key_id]): + # nonce is part of the request signing scheme, so it is not logged here; presence is sufficient for triage logger.warning( 'Missing required signature headers', algorithm=algorithm, timestamp=timestamp_str, - nonce=nonce, + nonce_present=bool(nonce), signature_present=bool(signature_b64), key_id=key_id, compact=compact, @@ -379,8 +382,9 @@ def _validate_and_store_nonce(compact: str, jurisdiction: str, nonce: str) -> No 'Nonce reuse detected', compact=compact, jurisdiction=jurisdiction, - nonce=nonce, ) + # the nonce is part of the request signing scheme, so it is only logged at DEBUG level + logger.debug('Nonce reuse detected details', nonce=nonce) raise CCUnauthorizedCustomResponseException('Nonce has already been used') from e logger.error('Failed to validate nonce', error=str(e), compact=compact, jurisdiction=jurisdiction) raise CCUnauthorizedException('Failed to validate nonce') from e diff --git a/backend/cosmetology-app/lambdas/python/common/cc_common/utils.py b/backend/cosmetology-app/lambdas/python/common/cc_common/utils.py index c1d73184e2..314ff796cf 100644 --- a/backend/cosmetology-app/lambdas/python/common/cc_common/utils.py +++ b/backend/cosmetology-app/lambdas/python/common/cc_common/utils.py @@ -113,15 +113,18 @@ def caught_handler(event, context: LambdaContext): content_type = event['headers'].get('Content-Type') - # Propagate these keys to all log messages in this with block + # Propagate these keys to all log messages in this with block. + # The caller's Cognito username (their email address) is intentionally excluded here, since the + # 'sub' claim already provides a non-PII identifier that is sufficient for tracing requests to a user. + # Similarly, only query parameter *names* (not values) are logged, since query parameter values could + # contain PII or other sensitive data depending on the endpoint. with logger.append_context_keys( method=event['httpMethod'], origin=origin, path=event['requestContext']['resourcePath'], content_type=content_type, identity={'user': event['requestContext'].get('authorizer', {}).get('claims', {}).get('sub')}, - query_params=event['queryStringParameters'], - username=event['requestContext'].get('authorizer', {}).get('claims', {}).get('cognito:username'), + query_param_keys=sorted((event.get('queryStringParameters') or {}).keys()), ): logger.info('Incoming request') diff --git a/backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/bulk_upload.py b/backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/bulk_upload.py index 4070c9662e..9efabed93b 100644 --- a/backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/bulk_upload.py +++ b/backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/bulk_upload.py @@ -194,9 +194,12 @@ def process_bulk_upload_file( 'Invalid license in line %s uploaded: %s', i + 1, str(e), - valid_data=report_license_data, + compact=compact, + jurisdiction=jurisdiction, exc_info=e, ) + # valid_data may contain licensee PII (name, license number, npi), so it is only logged at DEBUG + logger.debug('Invalid license record details', record_number=i + 1, valid_data=report_license_data) event_writer.put_event( Entry={ 'Source': f'org.compactconnect.bulk-ingest.{object_key}', diff --git a/backend/cosmetology-app/lambdas/python/search/handlers/populate_provider_documents.py b/backend/cosmetology-app/lambdas/python/search/handlers/populate_provider_documents.py index 30b35f0a16..d9d40ee46e 100644 --- a/backend/cosmetology-app/lambdas/python/search/handlers/populate_provider_documents.py +++ b/backend/cosmetology-app/lambdas/python/search/handlers/populate_provider_documents.py @@ -255,7 +255,13 @@ def populate_provider_documents(event: dict, context: LambdaContext): provider_id = provider_record.get('providerId') if not provider_id: - logger.warning('Provider record missing providerId', record=provider_record) + logger.warning( + 'Provider record missing providerId', + pk=provider_record.get('pk'), + sk=provider_record.get('sk'), + ) + # the record may contain PII (name, DOB, etc.), so it is only logged at DEBUG level + logger.debug('Provider record missing providerId details', record=provider_record) compact_stats['providers_failed'] += 1 continue diff --git a/backend/cosmetology-app/lambdas/python/staff-user-pre-token/main.py b/backend/cosmetology-app/lambdas/python/staff-user-pre-token/main.py index 07fa0ff5c1..8908b12ef6 100644 --- a/backend/cosmetology-app/lambdas/python/staff-user-pre-token/main.py +++ b/backend/cosmetology-app/lambdas/python/staff-user-pre-token/main.py @@ -14,7 +14,9 @@ @logger.inject_lambda_context() def customize_scopes(event: dict, context: LambdaContext): # noqa: ARG001 unused-argument """Customize the scopes in the access token before AWS generates and issues it""" - logger.info('Received event', event=event) + # The full event includes PII from Cognito user attributes (email, given/family name), so it is only + # logged at DEBUG level. + logger.debug('Received event', event=event) try: sub = event['request']['userAttributes']['sub'] @@ -26,6 +28,8 @@ def customize_scopes(event: dict, context: LambdaContext): # noqa: ARG001 unuse event['response']['claimsAndScopeOverrideDetails'] = None return event + logger.info('Customizing scopes for user', sub=sub) + try: user_data = UserData(sub) logger.debug('Adding scopes', scopes=user_data.scopes) diff --git a/backend/cosmetology-app/stacks/persistent_stack/staff_users.py b/backend/cosmetology-app/stacks/persistent_stack/staff_users.py index d9272301c4..0e5246f1cb 100644 --- a/backend/cosmetology-app/stacks/persistent_stack/staff_users.py +++ b/backend/cosmetology-app/stacks/persistent_stack/staff_users.py @@ -133,10 +133,10 @@ def _add_scope_customization(self, stack: ps.PersistentStack): handler='customize_scopes', alarm_topic=stack.alarm_topic, environment={ - 'DEBUG': 'true', 'USERS_TABLE_NAME': self.user_table.table_name, 'COMPACTS': json.dumps(compacts), 'JURISDICTIONS': json.dumps(jurisdictions), + # spread last so environment-appropriate values (e.g. DEBUG) are not clobbered by the above **stack.common_env_vars, }, ) diff --git a/backend/social-work-app/lambdas/nodejs/cognito-emails/lambda.ts b/backend/social-work-app/lambdas/nodejs/cognito-emails/lambda.ts index dda0a42946..5e799cdcc1 100644 --- a/backend/social-work-app/lambdas/nodejs/cognito-emails/lambda.ts +++ b/backend/social-work-app/lambdas/nodejs/cognito-emails/lambda.ts @@ -82,7 +82,7 @@ export class Lambda implements LambdaInterface { logger.info('Processing Cognito custom message event', { triggerSource: event.triggerSource, userPoolId: event.userPoolId, - userName: event.userName + userName: this.emailService.maskEmail(event.userName) }); try { diff --git a/backend/social-work-app/lambdas/nodejs/lib/email/base-email-service.ts b/backend/social-work-app/lambdas/nodejs/lib/email/base-email-service.ts index 7f7689658c..5454c9c594 100644 --- a/backend/social-work-app/lambdas/nodejs/lib/email/base-email-service.ts +++ b/backend/social-work-app/lambdas/nodejs/lib/email/base-email-service.ts @@ -64,6 +64,20 @@ export abstract class BaseEmailService { return `${environmentVariableService.getUiBasePathUrl()}/img/email`; } + public maskEmail(email: string): string { + const at = email.indexOf('@'); + + if (at <= 0) { + return '***'; + } + + return `${email[0]}***${email.slice(at)}`; + } + + public maskEmails(emails: string[]): string[] { + return emails.map((email) => this.maskEmail(email)); + } + protected async sendEmail({ htmlContent, subject, recipients, errorMessage }: {htmlContent: string, subject: string, recipients: string[], errorMessage: string}) { try { diff --git a/backend/social-work-app/lambdas/nodejs/lib/email/ingest-event-email-service.ts b/backend/social-work-app/lambdas/nodejs/lib/email/ingest-event-email-service.ts index 2e07868188..1db740c3c0 100644 --- a/backend/social-work-app/lambdas/nodejs/lib/email/ingest-event-email-service.ts +++ b/backend/social-work-app/lambdas/nodejs/lib/email/ingest-event-email-service.ts @@ -17,7 +17,7 @@ export class IngestEventEmailService extends BaseEmailService { jurisdiction: string, recipients: string[] ) { - this.logger.info('Sending report email', { recipients: recipients }); + this.logger.info('Sending report email', { recipients: this.maskEmails(recipients) }); // Generate the HTML report const htmlContent = this.generateReport(events, compactName, jurisdiction); @@ -31,7 +31,7 @@ export class IngestEventEmailService extends BaseEmailService { } public async sendAllsWellEmail(compactName: string, jurisdiction: string, recipients: string[]) { - this.logger.info('Sending alls well email', { recipients: recipients }); + this.logger.info('Sending alls well email', { recipients: this.maskEmails(recipients) }); // Generate the HTML report const report = this.getNewEmailTemplate(); @@ -52,7 +52,7 @@ export class IngestEventEmailService extends BaseEmailService { } public async sendNoLicenseUpdatesEmail(compactName: string, jurisdiction: string, recipients: string[]) { - this.logger.info('Sending no license updates email', { recipients: recipients }); + this.logger.info('Sending no license updates email', { recipients: this.maskEmails(recipients) }); // Generate the HTML report const report = this.getNewEmailTemplate(); diff --git a/backend/social-work-app/lambdas/python/common/cc_common/data_model/data_client.py b/backend/social-work-app/lambdas/python/common/cc_common/data_model/data_client.py index 1e8a03df74..960557990e 100644 --- a/backend/social-work-app/lambdas/python/common/cc_common/data_model/data_client.py +++ b/backend/social-work-app/lambdas/python/common/cc_common/data_model/data_client.py @@ -85,7 +85,7 @@ def get_ssn_by_provider_id(self, *, compact: str, provider_id: str) -> str: raise CCInternalException(f'Expected 1 SSN index record, got {len(resp)}') return resp[0]['ssn'] - @logger_inject_kwargs(logger, 'compact', 'jurisdiction', 'family_name', 'given_name') + @logger_inject_kwargs(logger, 'compact', 'jurisdiction') def find_matching_license_record( self, *, @@ -109,6 +109,8 @@ def find_matching_license_record( :return: The matching license record if found, None otherwise """ logger.info('Querying license records', compact=compact, state=jurisdiction) + # family_name/given_name are PII, so they are only logged at DEBUG level + logger.debug('Querying license records details', family_name=family_name, given_name=given_name) resp = self.config.provider_table.query( IndexName=self.config.license_gsi_name, @@ -206,7 +208,7 @@ def get_provider_user_records( return ProviderUserRecords(resp['Items']) @paginated_query(set_query_limit_to_match_page_size=False) - @logger_inject_kwargs(logger, 'compact', 'provider_name', 'jurisdiction') + @logger_inject_kwargs(logger, 'compact', 'jurisdiction') def get_providers_sorted_by_family_name( self, *, @@ -217,6 +219,8 @@ def get_providers_sorted_by_family_name( scan_forward: bool = True, ): logger.info('Getting providers by family name') + # provider_name is PII, so it is only logged at DEBUG level + logger.debug('Getting providers by family name details', provider_name=provider_name) # Create a name value to use in key condition if name fields are provided name_value = None diff --git a/backend/social-work-app/lambdas/python/common/cc_common/data_model/user_client.py b/backend/social-work-app/lambdas/python/common/cc_common/data_model/user_client.py index fcbbfceab3..eea8686687 100644 --- a/backend/social-work-app/lambdas/python/common/cc_common/data_model/user_client.py +++ b/backend/social-work-app/lambdas/python/common/cc_common/data_model/user_client.py @@ -302,7 +302,9 @@ def create_user(self, compact: str, attributes: dict, permissions: dict): :param dict permissions: The permissions for the user :return: """ - logger.info('Creating staff user', attributes=attributes) + logger.info('Creating staff user', compact=compact) + # attributes contains PII (email, given/family name), so it is only logged at DEBUG level + logger.debug('Creating staff user with attributes', attributes=attributes) attributes = self.user_attributes_schema.load(attributes) permissions = self.compact_permissions_schema.load(permissions) @@ -329,7 +331,7 @@ def create_user(self, compact: str, attributes: dict, permissions: dict): # If the user was previously disabled, re-enable them if not resp.get('Enabled', True): - logger.info('Re-enabling previously disabled user', user_id=user_id, email=attributes['email']) + logger.info('Re-enabling previously disabled user', user_id=user_id) self.config.cognito_client.admin_enable_user( UserPoolId=self.config.user_pool_id, Username=attributes['email'] ) diff --git a/backend/social-work-app/lambdas/python/common/cc_common/signature_auth.py b/backend/social-work-app/lambdas/python/common/cc_common/signature_auth.py index dce8c4dcdd..961962f981 100644 --- a/backend/social-work-app/lambdas/python/common/cc_common/signature_auth.py +++ b/backend/social-work-app/lambdas/python/common/cc_common/signature_auth.py @@ -185,7 +185,9 @@ def _validate_nonce_format(nonce: str) -> None: import re if not re.match(r'^[a-zA-Z0-9-]+$', nonce): - logger.warning('Invalid nonce format - contains invalid characters', nonce=nonce) + logger.warning('Invalid nonce format - contains invalid characters') + # the nonce is part of the request signing scheme, so it is only logged at DEBUG level + logger.debug('Invalid nonce format details', nonce=nonce) raise CCUnauthorizedCustomResponseException('Nonce can only contain alphanumeric characters and hyphens') @@ -216,11 +218,12 @@ def _validate_signature(event: dict, compact: str, jurisdiction: str, public_key # Validate all required headers are present if not all([algorithm, timestamp_str, nonce, signature_b64, key_id]): + # nonce is part of the request signing scheme, so it is not logged here; presence is sufficient for triage logger.warning( 'Missing required signature headers', algorithm=algorithm, timestamp=timestamp_str, - nonce=nonce, + nonce_present=bool(nonce), signature_present=bool(signature_b64), key_id=key_id, compact=compact, @@ -379,8 +382,9 @@ def _validate_and_store_nonce(compact: str, jurisdiction: str, nonce: str) -> No 'Nonce reuse detected', compact=compact, jurisdiction=jurisdiction, - nonce=nonce, ) + # the nonce is part of the request signing scheme, so it is only logged at DEBUG level + logger.debug('Nonce reuse detected details', nonce=nonce) raise CCUnauthorizedCustomResponseException('Nonce has already been used') from e logger.error('Failed to validate nonce', error=str(e), compact=compact, jurisdiction=jurisdiction) raise CCUnauthorizedException('Failed to validate nonce') from e diff --git a/backend/social-work-app/lambdas/python/common/cc_common/utils.py b/backend/social-work-app/lambdas/python/common/cc_common/utils.py index 5ed3d794cb..beb8bdfa6c 100644 --- a/backend/social-work-app/lambdas/python/common/cc_common/utils.py +++ b/backend/social-work-app/lambdas/python/common/cc_common/utils.py @@ -113,15 +113,18 @@ def caught_handler(event, context: LambdaContext): content_type = event['headers'].get('Content-Type') - # Propagate these keys to all log messages in this with block + # Propagate these keys to all log messages in this with block. + # The caller's Cognito username (their email address) is intentionally excluded here, since the + # 'sub' claim already provides a non-PII identifier that is sufficient for tracing requests to a user. + # Similarly, only query parameter *names* (not values) are logged, since query parameter values could + # contain PII or other sensitive data depending on the endpoint. with logger.append_context_keys( method=event['httpMethod'], origin=origin, path=event['requestContext']['resourcePath'], content_type=content_type, identity={'user': event['requestContext'].get('authorizer', {}).get('claims', {}).get('sub')}, - query_params=event['queryStringParameters'], - username=event['requestContext'].get('authorizer', {}).get('claims', {}).get('cognito:username'), + query_param_keys=sorted((event.get('queryStringParameters') or {}).keys()), ): logger.info('Incoming request') diff --git a/backend/social-work-app/lambdas/python/search/handlers/populate_provider_documents.py b/backend/social-work-app/lambdas/python/search/handlers/populate_provider_documents.py index c3c2e3cce4..00e6629993 100644 --- a/backend/social-work-app/lambdas/python/search/handlers/populate_provider_documents.py +++ b/backend/social-work-app/lambdas/python/search/handlers/populate_provider_documents.py @@ -255,7 +255,13 @@ def populate_provider_documents(event: dict, context: LambdaContext): provider_id = provider_record.get('providerId') if not provider_id: - logger.warning('Provider record missing providerId', record=provider_record) + logger.warning( + 'Provider record missing providerId', + pk=provider_record.get('pk'), + sk=provider_record.get('sk'), + ) + # the record may contain PII (name, DOB, etc.), so it is only logged at DEBUG level + logger.debug('Provider record missing providerId details', record=provider_record) compact_stats['providers_failed'] += 1 continue diff --git a/backend/social-work-app/lambdas/python/staff-user-pre-token/main.py b/backend/social-work-app/lambdas/python/staff-user-pre-token/main.py index 07fa0ff5c1..8908b12ef6 100644 --- a/backend/social-work-app/lambdas/python/staff-user-pre-token/main.py +++ b/backend/social-work-app/lambdas/python/staff-user-pre-token/main.py @@ -14,7 +14,9 @@ @logger.inject_lambda_context() def customize_scopes(event: dict, context: LambdaContext): # noqa: ARG001 unused-argument """Customize the scopes in the access token before AWS generates and issues it""" - logger.info('Received event', event=event) + # The full event includes PII from Cognito user attributes (email, given/family name), so it is only + # logged at DEBUG level. + logger.debug('Received event', event=event) try: sub = event['request']['userAttributes']['sub'] @@ -26,6 +28,8 @@ def customize_scopes(event: dict, context: LambdaContext): # noqa: ARG001 unuse event['response']['claimsAndScopeOverrideDetails'] = None return event + logger.info('Customizing scopes for user', sub=sub) + try: user_data = UserData(sub) logger.debug('Adding scopes', scopes=user_data.scopes) diff --git a/backend/social-work-app/stacks/persistent_stack/staff_users.py b/backend/social-work-app/stacks/persistent_stack/staff_users.py index 324754a522..ff3079185a 100644 --- a/backend/social-work-app/stacks/persistent_stack/staff_users.py +++ b/backend/social-work-app/stacks/persistent_stack/staff_users.py @@ -133,10 +133,10 @@ def _add_scope_customization(self, stack: ps.PersistentStack): handler='customize_scopes', alarm_topic=stack.alarm_topic, environment={ - 'DEBUG': 'true', 'USERS_TABLE_NAME': self.user_table.table_name, 'COMPACTS': json.dumps(compacts), 'JURISDICTIONS': json.dumps(jurisdictions), + # spread last so environment-appropriate values (e.g. DEBUG) are not clobbered by the above **stack.common_env_vars, }, ) From 6047f29228acbf876a7b01c62ebe90f0c86f519b Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Wed, 29 Jul 2026 11:17:12 -0500 Subject: [PATCH 10/14] Set min items requirement at API Gateway layer --- backend/social-work-app/stacks/api_stack/v1_api/api_model.py | 1 + .../resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json | 1 + .../snapshots/PATCH_LICENSE_INVESTIGATION_REQUEST_SCHEMA.json | 1 + .../snapshots/PATCH_PRIVILEGE_INVESTIGATION_REQUEST_SCHEMA.json | 1 + .../snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json | 1 + 5 files changed, 5 insertions(+) diff --git a/backend/social-work-app/stacks/api_stack/v1_api/api_model.py b/backend/social-work-app/stacks/api_stack/v1_api/api_model.py index 576d0d86ed..56cfcb0eac 100644 --- a/backend/social-work-app/stacks/api_stack/v1_api/api_model.py +++ b/backend/social-work-app/stacks/api_stack/v1_api/api_model.py @@ -782,6 +782,7 @@ def _clinical_privilege_action_categories_schema(self) -> JsonSchema: return JsonSchema( type=JsonSchemaType.ARRAY, description='The categories of clinical privilege action', + min_items=1, items=JsonSchema( type=JsonSchemaType.STRING, enum=[ diff --git a/backend/social-work-app/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json b/backend/social-work-app/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json index 5218ec23fd..cad090abba 100644 --- a/backend/social-work-app/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json +++ b/backend/social-work-app/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json @@ -52,6 +52,7 @@ ], "type": "string" }, + "minItems": 1, "type": "array" } }, diff --git a/backend/social-work-app/tests/resources/snapshots/PATCH_LICENSE_INVESTIGATION_REQUEST_SCHEMA.json b/backend/social-work-app/tests/resources/snapshots/PATCH_LICENSE_INVESTIGATION_REQUEST_SCHEMA.json index bc958df381..2a60d2915d 100644 --- a/backend/social-work-app/tests/resources/snapshots/PATCH_LICENSE_INVESTIGATION_REQUEST_SCHEMA.json +++ b/backend/social-work-app/tests/resources/snapshots/PATCH_LICENSE_INVESTIGATION_REQUEST_SCHEMA.json @@ -67,6 +67,7 @@ ], "type": "string" }, + "minItems": 1, "type": "array" } }, diff --git a/backend/social-work-app/tests/resources/snapshots/PATCH_PRIVILEGE_INVESTIGATION_REQUEST_SCHEMA.json b/backend/social-work-app/tests/resources/snapshots/PATCH_PRIVILEGE_INVESTIGATION_REQUEST_SCHEMA.json index c58aa46f55..33869bc818 100644 --- a/backend/social-work-app/tests/resources/snapshots/PATCH_PRIVILEGE_INVESTIGATION_REQUEST_SCHEMA.json +++ b/backend/social-work-app/tests/resources/snapshots/PATCH_PRIVILEGE_INVESTIGATION_REQUEST_SCHEMA.json @@ -53,6 +53,7 @@ ], "type": "string" }, + "minItems": 1, "type": "array" } }, diff --git a/backend/social-work-app/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json b/backend/social-work-app/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json index 948ba2976e..00cd7e7824 100644 --- a/backend/social-work-app/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json +++ b/backend/social-work-app/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json @@ -45,6 +45,7 @@ ], "type": "string" }, + "minItems": 1, "type": "array" } }, From 84b923e4a2faccbaab78e327373f0e7f5eacb8e8 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Wed, 29 Jul 2026 11:31:35 -0500 Subject: [PATCH 11/14] update test snapshot --- .../resources/snapshots/GET_PROVIDER_RESPONSE_SCHEMA.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/social-work-app/tests/resources/snapshots/GET_PROVIDER_RESPONSE_SCHEMA.json b/backend/social-work-app/tests/resources/snapshots/GET_PROVIDER_RESPONSE_SCHEMA.json index d37a400534..d906462c05 100644 --- a/backend/social-work-app/tests/resources/snapshots/GET_PROVIDER_RESPONSE_SCHEMA.json +++ b/backend/social-work-app/tests/resources/snapshots/GET_PROVIDER_RESPONSE_SCHEMA.json @@ -135,6 +135,7 @@ ], "type": "string" }, + "minItems": 1, "type": "array" }, "liftingUser": { @@ -801,6 +802,7 @@ ], "type": "string" }, + "minItems": 1, "type": "array" }, "liftingUser": { @@ -1667,6 +1669,7 @@ ], "type": "string" }, + "minItems": 1, "type": "array" }, "liftingUser": { From c9656b57090ad25b8cd7785eb705cda15f0f8610 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Fri, 31 Jul 2026 14:01:12 -0500 Subject: [PATCH 12/14] Remove remaining PII findings from scan --- backend/common-cdk/common_constructs/stack.py | 2 ++ .../cognito-backup/handlers/cognito_backup.py | 23 +++++++++++++++++-- .../data_model/transaction_client.py | 5 ++++ .../common/cc_common/email_service_client.py | 14 +++++++++-- ...ses_email_identity_verification_handler.py | 5 +++- .../handlers/provider_s3_events.py | 10 ++++---- .../handlers/provider_users.py | 1 - .../cognito-backup/handlers/cognito_backup.py | 23 +++++++++++++++++-- .../common/cc_common/email_service_client.py | 14 +++++++++-- ...ses_email_identity_verification_handler.py | 5 +++- .../cognito-backup/handlers/cognito_backup.py | 23 +++++++++++++++++-- .../common/cc_common/email_service_client.py | 14 +++++++++-- ...ses_email_identity_verification_handler.py | 5 +++- 13 files changed, 124 insertions(+), 20 deletions(-) diff --git a/backend/common-cdk/common_constructs/stack.py b/backend/common-cdk/common_constructs/stack.py index 44f9f66de8..71cb3076ea 100644 --- a/backend/common-cdk/common_constructs/stack.py +++ b/backend/common-cdk/common_constructs/stack.py @@ -82,6 +82,8 @@ def common_env_vars(self): return { # DEBUG-level logging can include sensitive request/response data, so it must # not be enabled by default. It remains available to aid technical support teams. + # Environment variables may be updated for individual lambdas as needed through + # AWS by support staff with administrator access. 'DEBUG': 'false', 'ALLOWED_ORIGINS': json.dumps(self.allowed_origins), 'COMPACTS': json.dumps(self.node.get_context('compacts')), diff --git a/backend/compact-connect/lambdas/python/cognito-backup/handlers/cognito_backup.py b/backend/compact-connect/lambdas/python/cognito-backup/handlers/cognito_backup.py index 6ee51d265e..ee7d3b9af9 100644 --- a/backend/compact-connect/lambdas/python/cognito-backup/handlers/cognito_backup.py +++ b/backend/compact-connect/lambdas/python/cognito-backup/handlers/cognito_backup.py @@ -105,7 +105,12 @@ def _export_user_pool(self, export_timestamp: str) -> int: self._export_single_user(user, export_timestamp) users_exported += 1 except (ClientError, ValueError) as e: - logger.error('Failed to export user', username=user.get('Username', 'unknown'), error=str(e)) + # Username is the user's email address, so we log the non-PII 'sub' identifier instead + logger.error( + 'Failed to export user', + user_id=self._get_user_sub(user.get('Attributes', [])), + error=str(e), + ) raise # Check for more pages @@ -168,7 +173,12 @@ def _export_single_user(self, user_data: dict[str, Any], export_timestamp: str) logger.debug('Exported user to S3', username=username, object_key=object_key) except ClientError as e: - logger.error('Failed to upload user to S3', username=username, error=str(e)) + # Username is the user's email address, so we log the non-PII 'sub' identifier instead + logger.error( + 'Failed to upload user to S3', + user_id=self._get_user_sub(user_data.get('Attributes', [])), + error=str(e), + ) raise def _extract_user_attributes(self, attributes: list[dict[str, str]]) -> dict[str, str]: @@ -180,6 +190,15 @@ def _extract_user_attributes(self, attributes: list[dict[str, str]]) -> dict[str """ return {attr['Name']: attr['Value'] for attr in attributes} + def _get_user_sub(self, attributes: list[dict[str, str]]) -> str: + """ + Extract the non-PII Cognito 'sub' identifier from a list of user attributes, for use in logging. + + :param attributes: List of Cognito user attributes + :return: The user's 'sub' value, or 'unknown' if not present + """ + return next((attr['Value'] for attr in attributes if attr['Name'] == 'sub'), 'unknown') + def backup_handler(event: dict[str, Any], context: Any) -> dict[str, Any]: # noqa: ARG001 unused-argument """ diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/transaction_client.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/transaction_client.py index 0ef086eb27..a77f8340c3 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/transaction_client.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/transaction_client.py @@ -260,6 +260,8 @@ def add_privilege_information_to_transactions( line_items=line_items, item_id_prefix=item_id_prefix, privilege_id=privilege_id ) else: + # raw records can contain PII (e.g. a deactivating staff user's name), so the full items are + # only logged at DEBUG level; the fields below are sufficient for triage at ERROR level logger.error( 'No matching jurisdiction privilege record found for transaction. ' 'Cannot determine privilege id for this transaction', @@ -267,6 +269,9 @@ def add_privilege_information_to_transactions( transactionId=transaction.transactionId, jurisdiction=jurisdiction, provider_id=transaction.licenseeId, + ) + logger.debug( + 'Matching privilege records for transaction', matching_privilege_records=response.get('Items', []), ) # we set the privilege id to UNKNOWN, so that it will be visible in the report 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 e592a80138..ef5700bd8e 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 @@ -101,13 +101,23 @@ def _invoke_lambda(self, payload: dict[str, Any]) -> dict[str, Any]: if response.get('FunctionError'): error_message = f'Failed to send email notification: {response.get("FunctionError")}' - self._logger.error(error_message) + self._logger.error(error_message, template=payload.get('template')) raise CCInternalException(error_message) return response except Exception as e: error_message = f'Error invoking email notification service lambda: {str(e)}' - self._logger.error(error_message, payload=payload, exception=str(e)) + # payload may contain PII (specificEmails, templateVariables with provider names), so it is only + # logged at DEBUG level; the non-PII fields below are sufficient for triage at ERROR level + self._logger.error( + error_message, + template=payload.get('template'), + compact=payload.get('compact'), + jurisdiction=payload.get('jurisdiction'), + recipient_type=payload.get('recipientType'), + exception=str(e), + ) + self._logger.debug('Email notification service invocation payload', payload=payload) raise CCInternalException(error_message) from e def send_provider_privilege_deactivation_email( diff --git a/backend/compact-connect/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py b/backend/compact-connect/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py index a9ca94bf70..e9d6e5fcfa 100644 --- a/backend/compact-connect/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py +++ b/backend/compact-connect/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py @@ -26,7 +26,10 @@ def on_event(event: dict, context: LambdaContext): # noqa: ARG001 unused-argume :param context: The Lambda context :return: Physical resource ID on success """ - logger.info('Entering SES email identity verification handler', event=json.dumps(event)) + # The full event includes a pre-signed 'ResponseURL' (with an access key id and signature) used to signal + # CloudFormation, so it is only logged at DEBUG level. + logger.info('Entering SES email identity verification handler', request_type=event.get('RequestType')) + logger.debug('SES email identity verification handler event', event=json.dumps(event)) properties = event['ResourceProperties'] request_type = event['RequestType'] match request_type: diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/provider_s3_events.py b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/provider_s3_events.py index 37a845d436..b36b7be940 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/provider_s3_events.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/provider_s3_events.py @@ -9,13 +9,14 @@ def process_provider_s3_events(event: dict, context: LambdaContext): # noqa: AR :param event: Standard S3 ObjectCreated event :param LambdaContext context: """ - logger.info('Received event', event=event) + # The S3 object key can embed the original user-supplied filename (e.g. a military document upload), so the + # full event/key is only logged at DEBUG level. + logger.debug('Received event', event=event) try: for record in event['Records']: bucket_name = record['s3']['bucket']['name'] key = record['s3']['object']['key'] - size = record['s3']['object']['size'] - logger.info('Object', s3_url=f's3://{bucket_name}/{key}', size=size) + logger.debug('Object key', s3_url=f's3://{bucket_name}/{key}') # "ObjectCreated:Copy" events fire when the SSN-correction migration copies a practitioner's # documents from the old provider id's keyspace to the new one (see @@ -35,7 +36,8 @@ def process_provider_s3_events(event: dict, context: LambdaContext): # noqa: AR # we split the key to get the various parts needed to query for the record key_parts = key.split('/') if len(key_parts) < 5: - logger.error('Invalid key format', key=key) + logger.error('Invalid key format') + logger.debug('Invalid key', key=key) return compact = key_parts[1] diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/provider_users.py b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/provider_users.py index 6a275d24b2..f0d51dbd8e 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/handlers/provider_users.py +++ b/backend/compact-connect/lambdas/python/provider-data-v1/handlers/provider_users.py @@ -389,7 +389,6 @@ def _post_provider_email_verify(event: dict, context: LambdaContext): # noqa: A 'Email address became unavailable during verification process', compact=compact, provider_id=provider_id, - new_email=new_email, ) # Clear the verification data since the email is no longer available config.data_client.clear_provider_email_verification_data( diff --git a/backend/cosmetology-app/lambdas/python/cognito-backup/handlers/cognito_backup.py b/backend/cosmetology-app/lambdas/python/cognito-backup/handlers/cognito_backup.py index 6ee51d265e..ee7d3b9af9 100644 --- a/backend/cosmetology-app/lambdas/python/cognito-backup/handlers/cognito_backup.py +++ b/backend/cosmetology-app/lambdas/python/cognito-backup/handlers/cognito_backup.py @@ -105,7 +105,12 @@ def _export_user_pool(self, export_timestamp: str) -> int: self._export_single_user(user, export_timestamp) users_exported += 1 except (ClientError, ValueError) as e: - logger.error('Failed to export user', username=user.get('Username', 'unknown'), error=str(e)) + # Username is the user's email address, so we log the non-PII 'sub' identifier instead + logger.error( + 'Failed to export user', + user_id=self._get_user_sub(user.get('Attributes', [])), + error=str(e), + ) raise # Check for more pages @@ -168,7 +173,12 @@ def _export_single_user(self, user_data: dict[str, Any], export_timestamp: str) logger.debug('Exported user to S3', username=username, object_key=object_key) except ClientError as e: - logger.error('Failed to upload user to S3', username=username, error=str(e)) + # Username is the user's email address, so we log the non-PII 'sub' identifier instead + logger.error( + 'Failed to upload user to S3', + user_id=self._get_user_sub(user_data.get('Attributes', [])), + error=str(e), + ) raise def _extract_user_attributes(self, attributes: list[dict[str, str]]) -> dict[str, str]: @@ -180,6 +190,15 @@ def _extract_user_attributes(self, attributes: list[dict[str, str]]) -> dict[str """ return {attr['Name']: attr['Value'] for attr in attributes} + def _get_user_sub(self, attributes: list[dict[str, str]]) -> str: + """ + Extract the non-PII Cognito 'sub' identifier from a list of user attributes, for use in logging. + + :param attributes: List of Cognito user attributes + :return: The user's 'sub' value, or 'unknown' if not present + """ + return next((attr['Value'] for attr in attributes if attr['Name'] == 'sub'), 'unknown') + def backup_handler(event: dict[str, Any], context: Any) -> dict[str, Any]: # noqa: ARG001 unused-argument """ diff --git a/backend/cosmetology-app/lambdas/python/common/cc_common/email_service_client.py b/backend/cosmetology-app/lambdas/python/common/cc_common/email_service_client.py index 96ac6aafc7..8f9848db1a 100644 --- a/backend/cosmetology-app/lambdas/python/common/cc_common/email_service_client.py +++ b/backend/cosmetology-app/lambdas/python/common/cc_common/email_service_client.py @@ -96,13 +96,23 @@ def _invoke_lambda(self, payload: dict[str, Any]) -> dict[str, Any]: if response.get('FunctionError'): error_message = f'Failed to send email notification: {response.get("FunctionError")}' - self._logger.error(error_message, payload=payload) + self._logger.error(error_message, template=payload.get('template')) raise CCInternalException(error_message) return response except Exception as e: error_message = f'Error invoking email notification service lambda: {str(e)}' - self._logger.error(error_message, payload=payload, exception=str(e)) + # payload may contain PII (specificEmails, templateVariables with provider names), so it is only + # logged at DEBUG level; the non-PII fields below are sufficient for triage at ERROR level + self._logger.error( + error_message, + template=payload.get('template'), + compact=payload.get('compact'), + jurisdiction=payload.get('jurisdiction'), + recipient_type=payload.get('recipientType'), + exception=str(e), + ) + self._logger.debug('Email notification service invocation payload', payload=payload) raise CCInternalException(error_message) from e def send_license_encumbrance_state_notification_email( diff --git a/backend/cosmetology-app/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py b/backend/cosmetology-app/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py index a9ca94bf70..e9d6e5fcfa 100644 --- a/backend/cosmetology-app/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py +++ b/backend/cosmetology-app/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py @@ -26,7 +26,10 @@ def on_event(event: dict, context: LambdaContext): # noqa: ARG001 unused-argume :param context: The Lambda context :return: Physical resource ID on success """ - logger.info('Entering SES email identity verification handler', event=json.dumps(event)) + # The full event includes a pre-signed 'ResponseURL' (with an access key id and signature) used to signal + # CloudFormation, so it is only logged at DEBUG level. + logger.info('Entering SES email identity verification handler', request_type=event.get('RequestType')) + logger.debug('SES email identity verification handler event', event=json.dumps(event)) properties = event['ResourceProperties'] request_type = event['RequestType'] match request_type: diff --git a/backend/social-work-app/lambdas/python/cognito-backup/handlers/cognito_backup.py b/backend/social-work-app/lambdas/python/cognito-backup/handlers/cognito_backup.py index 6ee51d265e..ee7d3b9af9 100644 --- a/backend/social-work-app/lambdas/python/cognito-backup/handlers/cognito_backup.py +++ b/backend/social-work-app/lambdas/python/cognito-backup/handlers/cognito_backup.py @@ -105,7 +105,12 @@ def _export_user_pool(self, export_timestamp: str) -> int: self._export_single_user(user, export_timestamp) users_exported += 1 except (ClientError, ValueError) as e: - logger.error('Failed to export user', username=user.get('Username', 'unknown'), error=str(e)) + # Username is the user's email address, so we log the non-PII 'sub' identifier instead + logger.error( + 'Failed to export user', + user_id=self._get_user_sub(user.get('Attributes', [])), + error=str(e), + ) raise # Check for more pages @@ -168,7 +173,12 @@ def _export_single_user(self, user_data: dict[str, Any], export_timestamp: str) logger.debug('Exported user to S3', username=username, object_key=object_key) except ClientError as e: - logger.error('Failed to upload user to S3', username=username, error=str(e)) + # Username is the user's email address, so we log the non-PII 'sub' identifier instead + logger.error( + 'Failed to upload user to S3', + user_id=self._get_user_sub(user_data.get('Attributes', [])), + error=str(e), + ) raise def _extract_user_attributes(self, attributes: list[dict[str, str]]) -> dict[str, str]: @@ -180,6 +190,15 @@ def _extract_user_attributes(self, attributes: list[dict[str, str]]) -> dict[str """ return {attr['Name']: attr['Value'] for attr in attributes} + def _get_user_sub(self, attributes: list[dict[str, str]]) -> str: + """ + Extract the non-PII Cognito 'sub' identifier from a list of user attributes, for use in logging. + + :param attributes: List of Cognito user attributes + :return: The user's 'sub' value, or 'unknown' if not present + """ + return next((attr['Value'] for attr in attributes if attr['Name'] == 'sub'), 'unknown') + def backup_handler(event: dict[str, Any], context: Any) -> dict[str, Any]: # noqa: ARG001 unused-argument """ diff --git a/backend/social-work-app/lambdas/python/common/cc_common/email_service_client.py b/backend/social-work-app/lambdas/python/common/cc_common/email_service_client.py index 10cc4feeaa..c2f5db953f 100644 --- a/backend/social-work-app/lambdas/python/common/cc_common/email_service_client.py +++ b/backend/social-work-app/lambdas/python/common/cc_common/email_service_client.py @@ -96,13 +96,23 @@ def _invoke_lambda(self, payload: dict[str, Any]) -> dict[str, Any]: if response.get('FunctionError'): error_message = f'Failed to send email notification: {response.get("FunctionError")}' - self._logger.error(error_message, payload=payload) + self._logger.error(error_message, template=payload.get('template')) raise CCInternalException(error_message) return response except Exception as e: error_message = f'Error invoking email notification service lambda: {str(e)}' - self._logger.error(error_message, payload=payload, exception=str(e)) + # payload may contain PII (specificEmails, templateVariables with provider names), so it is only + # logged at DEBUG level; the non-PII fields below are sufficient for triage at ERROR level + self._logger.error( + error_message, + template=payload.get('template'), + compact=payload.get('compact'), + jurisdiction=payload.get('jurisdiction'), + recipient_type=payload.get('recipientType'), + exception=str(e), + ) + self._logger.debug('Email notification service invocation payload', payload=payload) raise CCInternalException(error_message) from e def send_license_encumbrance_state_notification_email( diff --git a/backend/social-work-app/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py b/backend/social-work-app/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py index a9ca94bf70..e9d6e5fcfa 100644 --- a/backend/social-work-app/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py +++ b/backend/social-work-app/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py @@ -26,7 +26,10 @@ def on_event(event: dict, context: LambdaContext): # noqa: ARG001 unused-argume :param context: The Lambda context :return: Physical resource ID on success """ - logger.info('Entering SES email identity verification handler', event=json.dumps(event)) + # The full event includes a pre-signed 'ResponseURL' (with an access key id and signature) used to signal + # CloudFormation, so it is only logged at DEBUG level. + logger.info('Entering SES email identity verification handler', request_type=event.get('RequestType')) + logger.debug('SES email identity verification handler event', event=json.dumps(event)) properties = event['ResourceProperties'] request_type = event['RequestType'] match request_type: From e5ba19b02a8d87512317d208efc1e8e62c89d3e0 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Fri, 31 Jul 2026 15:36:19 -0500 Subject: [PATCH 13/14] Feedback - remove verbose debug logs --- .../python/common/cc_common/email_service_client.py | 5 ++--- .../ses_email_identity_verification_handler.py | 11 ++++++----- .../python/common/cc_common/email_service_client.py | 5 ++--- .../ses_email_identity_verification_handler.py | 11 ++++++----- .../python/common/cc_common/email_service_client.py | 5 ++--- .../ses_email_identity_verification_handler.py | 11 ++++++----- 6 files changed, 24 insertions(+), 24 deletions(-) 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 ef5700bd8e..89fcc0b745 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 @@ -107,8 +107,8 @@ def _invoke_lambda(self, payload: dict[str, Any]) -> dict[str, Any]: return response except Exception as e: error_message = f'Error invoking email notification service lambda: {str(e)}' - # payload may contain PII (specificEmails, templateVariables with provider names), so it is only - # logged at DEBUG level; the non-PII fields below are sufficient for triage at ERROR level + # payload is never logged: it can contain PII and credentials (specificEmails, provider names, + # verificationCode, recoveryToken); the non-PII fields below are sufficient for triage self._logger.error( error_message, template=payload.get('template'), @@ -117,7 +117,6 @@ def _invoke_lambda(self, payload: dict[str, Any]) -> dict[str, Any]: recipient_type=payload.get('recipientType'), exception=str(e), ) - self._logger.debug('Email notification service invocation payload', payload=payload) raise CCInternalException(error_message) from e def send_provider_privilege_deactivation_email( diff --git a/backend/compact-connect/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py b/backend/compact-connect/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py index e9d6e5fcfa..6c96b251d3 100644 --- a/backend/compact-connect/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py +++ b/backend/compact-connect/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -import json import time import boto3 @@ -26,10 +25,12 @@ def on_event(event: dict, context: LambdaContext): # noqa: ARG001 unused-argume :param context: The Lambda context :return: Physical resource ID on success """ - # The full event includes a pre-signed 'ResponseURL' (with an access key id and signature) used to signal - # CloudFormation, so it is only logged at DEBUG level. - logger.info('Entering SES email identity verification handler', request_type=event.get('RequestType')) - logger.debug('SES email identity verification handler event', event=json.dumps(event)) + # The event is never logged in full: it includes a pre-signed 'ResponseURL' (with an access key id and + # signature) used to signal CloudFormation. + logger.info( + 'Entering SES email identity verification handler', + request_type=event.get('RequestType') + ) properties = event['ResourceProperties'] request_type = event['RequestType'] match request_type: diff --git a/backend/cosmetology-app/lambdas/python/common/cc_common/email_service_client.py b/backend/cosmetology-app/lambdas/python/common/cc_common/email_service_client.py index 8f9848db1a..772500626c 100644 --- a/backend/cosmetology-app/lambdas/python/common/cc_common/email_service_client.py +++ b/backend/cosmetology-app/lambdas/python/common/cc_common/email_service_client.py @@ -102,8 +102,8 @@ def _invoke_lambda(self, payload: dict[str, Any]) -> dict[str, Any]: return response except Exception as e: error_message = f'Error invoking email notification service lambda: {str(e)}' - # payload may contain PII (specificEmails, templateVariables with provider names), so it is only - # logged at DEBUG level; the non-PII fields below are sufficient for triage at ERROR level + # payload is never logged: it can contain PII and credentials (specificEmails, provider names, + # verificationCode, recoveryToken); the non-PII fields below are sufficient for triage self._logger.error( error_message, template=payload.get('template'), @@ -112,7 +112,6 @@ def _invoke_lambda(self, payload: dict[str, Any]) -> dict[str, Any]: recipient_type=payload.get('recipientType'), exception=str(e), ) - self._logger.debug('Email notification service invocation payload', payload=payload) raise CCInternalException(error_message) from e def send_license_encumbrance_state_notification_email( diff --git a/backend/cosmetology-app/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py b/backend/cosmetology-app/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py index e9d6e5fcfa..6c96b251d3 100644 --- a/backend/cosmetology-app/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py +++ b/backend/cosmetology-app/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -import json import time import boto3 @@ -26,10 +25,12 @@ def on_event(event: dict, context: LambdaContext): # noqa: ARG001 unused-argume :param context: The Lambda context :return: Physical resource ID on success """ - # The full event includes a pre-signed 'ResponseURL' (with an access key id and signature) used to signal - # CloudFormation, so it is only logged at DEBUG level. - logger.info('Entering SES email identity verification handler', request_type=event.get('RequestType')) - logger.debug('SES email identity verification handler event', event=json.dumps(event)) + # The event is never logged in full: it includes a pre-signed 'ResponseURL' (with an access key id and + # signature) used to signal CloudFormation. + logger.info( + 'Entering SES email identity verification handler', + request_type=event.get('RequestType') + ) properties = event['ResourceProperties'] request_type = event['RequestType'] match request_type: diff --git a/backend/social-work-app/lambdas/python/common/cc_common/email_service_client.py b/backend/social-work-app/lambdas/python/common/cc_common/email_service_client.py index c2f5db953f..cfbb344a23 100644 --- a/backend/social-work-app/lambdas/python/common/cc_common/email_service_client.py +++ b/backend/social-work-app/lambdas/python/common/cc_common/email_service_client.py @@ -102,8 +102,8 @@ def _invoke_lambda(self, payload: dict[str, Any]) -> dict[str, Any]: return response except Exception as e: error_message = f'Error invoking email notification service lambda: {str(e)}' - # payload may contain PII (specificEmails, templateVariables with provider names), so it is only - # logged at DEBUG level; the non-PII fields below are sufficient for triage at ERROR level + # payload is never logged: it can contain PII and credentials (specificEmails, provider names, + # verificationCode, recoveryToken); the non-PII fields below are sufficient for triage self._logger.error( error_message, template=payload.get('template'), @@ -112,7 +112,6 @@ def _invoke_lambda(self, payload: dict[str, Any]) -> dict[str, Any]: recipient_type=payload.get('recipientType'), exception=str(e), ) - self._logger.debug('Email notification service invocation payload', payload=payload) raise CCInternalException(error_message) from e def send_license_encumbrance_state_notification_email( diff --git a/backend/social-work-app/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py b/backend/social-work-app/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py index e9d6e5fcfa..6c96b251d3 100644 --- a/backend/social-work-app/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py +++ b/backend/social-work-app/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -import json import time import boto3 @@ -26,10 +25,12 @@ def on_event(event: dict, context: LambdaContext): # noqa: ARG001 unused-argume :param context: The Lambda context :return: Physical resource ID on success """ - # The full event includes a pre-signed 'ResponseURL' (with an access key id and signature) used to signal - # CloudFormation, so it is only logged at DEBUG level. - logger.info('Entering SES email identity verification handler', request_type=event.get('RequestType')) - logger.debug('SES email identity verification handler event', event=json.dumps(event)) + # The event is never logged in full: it includes a pre-signed 'ResponseURL' (with an access key id and + # signature) used to signal CloudFormation. + logger.info( + 'Entering SES email identity verification handler', + request_type=event.get('RequestType') + ) properties = event['ResourceProperties'] request_type = event['RequestType'] match request_type: From 8c811146fa8fc8701b240bde93c65ccf912d6a9a Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Fri, 31 Jul 2026 15:45:20 -0500 Subject: [PATCH 14/14] pin pip version in GH action runners --- .github/workflows/check-cosmetology-app.yml | 6 ++++-- .github/workflows/check-social-work-app.yml | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check-cosmetology-app.yml b/.github/workflows/check-cosmetology-app.yml index 2280324364..10dc9aec40 100644 --- a/.github/workflows/check-cosmetology-app.yml +++ b/.github/workflows/check-cosmetology-app.yml @@ -26,7 +26,8 @@ jobs: - name: Upgrade pip # Runner image ships pip 25.3; Upgrade to 26.1+ to include fix for CVE-2026-3219. - run: pip install --upgrade 'pip>=26.1' + # Held below 26.2, which drops pip._internal.utils.compat.stdlib_pkgs and breaks pip-sync. + run: pip install --upgrade 'pip>=26.1,<26.2' - name: Install dev dependencies run: "pip install -r backend/cosmetology-app/requirements-dev.txt" @@ -87,7 +88,8 @@ jobs: - name: Upgrade pip # Runner image ships pip 25.3; Upgrade to 26.1+ to include fix for CVE-2026-3219. - run: pip install --upgrade 'pip>=26.1' + # Held below 26.2, which drops pip._internal.utils.compat.stdlib_pkgs and breaks pip-sync. + run: pip install --upgrade 'pip>=26.1,<26.2' # Setup Node - name: Setup Node diff --git a/.github/workflows/check-social-work-app.yml b/.github/workflows/check-social-work-app.yml index 5be8925b80..1f785d22d7 100644 --- a/.github/workflows/check-social-work-app.yml +++ b/.github/workflows/check-social-work-app.yml @@ -26,7 +26,8 @@ jobs: - name: Upgrade pip # Runner image ships pip 25.3; Upgrade to 26.1+ to include fix for CVE-2026-3219. - run: pip install --upgrade 'pip>=26.1' + # Held below 26.2, which drops pip._internal.utils.compat.stdlib_pkgs and breaks pip-sync. + run: pip install --upgrade 'pip>=26.1,<26.2' - name: Install dev dependencies run: "pip install -r backend/social-work-app/requirements-dev.txt" @@ -87,7 +88,8 @@ jobs: - name: Upgrade pip # Runner image ships pip 25.3; Upgrade to 26.1+ to include fix for CVE-2026-3219. - run: pip install --upgrade 'pip>=26.1' + # Held below 26.2, which drops pip._internal.utils.compat.stdlib_pkgs and breaks pip-sync. + run: pip install --upgrade 'pip>=26.1,<26.2' # Setup Node - name: Setup Node