diff --git a/domain-cc/cc-app/src/python_src/api.py b/domain-cc/cc-app/src/python_src/api.py index 7641e7665e..19f94e977f 100644 --- a/domain-cc/cc-app/src/python_src/api.py +++ b/domain-cc/cc-app/src/python_src/api.py @@ -3,20 +3,17 @@ import sys import time from functools import wraps -from typing import Optional, Tuple +from typing import Tuple from fastapi import FastAPI, HTTPException, Request from .pydantic_models import ( - Claim, ClaimLinkInfo, ClassifiedContention, ClassifierResponse, Contention, - PredictedClassification, VaGovClaim, ) -from .util.brd_classification_codes import get_classification_name from .util.logging_dropdown_selections import build_logging_table from .util.lookup_table import ContentionTextLookupTable, DiagnosticCodeLookupTable from .util.sanitizer import sanitize_log @@ -69,31 +66,6 @@ def get_health_status(): return {"status": "ok"} -def log_lookup_table_match( - classification_code: int, - contention_text: str, -): - is_in_dropdown = contention_text.strip().lower() in dropdown_values - log_as_json({"is_in_dropdown": sanitize_log(is_in_dropdown)}) - log_contention_text = contention_text if is_in_dropdown else "Not in dropdown" - - if classification_code: - already_mapped_text = contention_text.strip().lower() # do not leak PII - log_as_json({"lookup_table_match": sanitize_log(already_mapped_text)}) - elif is_in_dropdown: - log_as_json( - { - "lookup_table_match": sanitize_log( - f"No table match for {log_contention_text}" - ) - } - ) - else: - log_as_json( - {"lookup_table_match": sanitize_log("No table match for free text entry")} - ) - - def log_as_json(log: dict): if "date" not in log.keys(): log.update({"date": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}) @@ -190,78 +162,6 @@ def wrapper(*args, **kwargs): return wrapper -def log_claim_stats(claim: Claim, classification: Optional[PredictedClassification]): - if classification: - classification_code = classification.classification_code - classification_name = classification.classification_name - else: - classification_code = None - classification_name = None - - contention_text = claim.contention_text or "" - is_in_dropdown = contention_text.strip().lower() in dropdown_values - is_mapped_text = dropdown_lookup_table.get(contention_text, None) is not None - log_contention_text = ( - contention_text if is_mapped_text else "unmapped contention text" - ) - - log_as_json( - { - "claim_id": sanitize_log(claim.claim_id), - "claim_type": sanitize_log(claim.claim_type), - "classification_code": classification_code, - "classification_name": classification_name, - "contention_text": log_contention_text, - "diagnostic_code": sanitize_log(claim.diagnostic_code), - "form526_submission_id": sanitize_log(claim.form526_submission_id), - "is_in_dropdown": is_in_dropdown, - "is_lookup_table_match": classification_code is not None, - } - ) - - -@app.post("/classifier", deprecated=True) -def get_classification(claim: Claim) -> Optional[PredictedClassification]: - """[DEPRECATED] Use /va-gov-claim-classifier instead""" - log_as_json( - { - "claim_id": sanitize_log(claim.claim_id), - "form526_submission_id": sanitize_log(claim.form526_submission_id), - } - ) - classification_code = None - if claim.claim_type == "claim_for_increase": - classification_code = dc_lookup_table.get(claim.diagnostic_code)[ - "classification_code" - ] - - if claim.contention_text and not classification_code: - classification_code = dropdown_lookup_table.get(claim.contention_text)[ - "classification_code" - ] - - if claim.claim_type == "new": - log_lookup_table_match(classification_code, claim.contention_text) - else: - log_as_json({"diagnostic code": sanitize_log(claim.diagnostic_code)}) - - if classification_code: - classification_name = get_classification_name(classification_code) - classification = { - "classification_code": classification_code, - "classification_name": classification_name, - } - else: - classification = None - - log_as_json({"classification": classification}) - log_claim_stats( - claim, - PredictedClassification(**classification) if classification else None, - ) - return classification - - @app.post("/claim-linker") def link_vbms_claim_id(claim_link_info: ClaimLinkInfo): log_as_json( diff --git a/domain-cc/cc-app/src/python_src/pydantic_models.py b/domain-cc/cc-app/src/python_src/pydantic_models.py index 081cab747b..30048fd412 100644 --- a/domain-cc/cc-app/src/python_src/pydantic_models.py +++ b/domain-cc/cc-app/src/python_src/pydantic_models.py @@ -5,37 +5,6 @@ from pydantic.types import conlist -class Claim(BaseModel): - claim_id: int - form526_submission_id: int - diagnostic_code: Optional[ - int - ] = None # only required for claim_type: "claim_for_increase" - claim_type: str = "claim_for_increase" - contention_text: Optional[ - str - ] = None # marked optional to retain compatibility with v1 - - @model_validator(mode="before") - @classmethod - def check_dc_for_cfi(cls, values): - claim_type = values.get("claim_type") - diagnostic_code = values.get("diagnostic_code") - - if claim_type == "claim_for_increase" and diagnostic_code is None: - raise ValueError( - "diagnostic_code is required for claim_type claim_for_increase" - ) - return values - - -class PredictedClassification(BaseModel): - """Prediction result of our ML model""" - - classification_code: int - classification_name: str - - class ClaimLinkInfo(BaseModel): """used for connecting VA.gov and VBMS claims to each other in order to track contention changes downstream""" @@ -46,9 +15,9 @@ class ClaimLinkInfo(BaseModel): class Contention(BaseModel): contention_text: str contention_type: str # "disabilityActionType" in the VA.gov API - diagnostic_code: Optional[ - int - ] = None # only required for contention_type: "claim_for_increase" + diagnostic_code: Optional[int] = ( + None # only required for contention_type: "claim_for_increase" + ) @model_validator(mode="before") @classmethod @@ -73,9 +42,9 @@ class VaGovClaim(BaseModel): class ClassifiedContention(BaseModel): classification_code: Optional[int] classification_name: Optional[str] - diagnostic_code: Optional[ - int - ] = None # only required for contention_type: "claim_for_increase" + diagnostic_code: Optional[int] = ( + None # only required for contention_type: "claim_for_increase" + ) contention_type: str # "disabilityActionType" in the VA.gov API diff --git a/domain-cc/cc-app/tests/test_api.py b/domain-cc/cc-app/tests/test_api.py deleted file mode 100644 index 7b38dc12d4..0000000000 --- a/domain-cc/cc-app/tests/test_api.py +++ /dev/null @@ -1,134 +0,0 @@ -from fastapi.testclient import TestClient - -from .conftest import ( - BENIGN_GROWTH_BRAIN_CLASSIFICATION, - DRUG_INDUCED_PULMONARY_PNEMONIA_CLASSIFICATION, - IMPAIRMENT_OF_FEMUR_CLASSIFICATION, - TUBERCULOSIS_CLASSIFICATION, -) - - -def test_classification(client: TestClient): - """old api inputs without contention text still operate the same""" - json_post_dict = { - "diagnostic_code": TUBERCULOSIS_CLASSIFICATION["diagnostic_code"], - "claim_id": 100, - "form526_submission_id": 500, - } - - response = client.post("/classifier", json=json_post_dict) - assert response.status_code == 200 - assert ( - response.json()["classification_code"] - == TUBERCULOSIS_CLASSIFICATION["classification_code"] - ) - assert ( - response.json()["classification_name"] - == TUBERCULOSIS_CLASSIFICATION["classification_name"] - ) - - -def test_missing_params(client: TestClient): - """should fail if all required params are not present""" - json_post_dict = { - "diagnostic_code": 6510, - } - - response = client.post("/classifier", json=json_post_dict) - assert response.status_code == 422 - - -def test_unmapped_diagnostic_code(client: TestClient): - """should return null""" - json_post_dict = { - "diagnostic_code": 7, - "claim_id": 700, - "form526_submission_id": 777, - } - response = client.post("/classifier", json=json_post_dict) - assert response.status_code == 200 - assert response.json() is None - - -def test_unprocessable_content(client: TestClient): - json_post_dict = { - "diagnostic_code": "this is personal information", - "claim_id": "SQL \n injection \n not really", - "form526_submission_id": "1-234-567-9999", - } - - response = client.post("/classifier", json=json_post_dict) - assert response.status_code == 422 - - -def test_v2_table_diagnostic_code(client: TestClient): - json_post_dict = { - "diagnostic_code": BENIGN_GROWTH_BRAIN_CLASSIFICATION["diagnostic_code"], - "claim_id": 123, - "form526_submission_id": 456, - } - - response = client.post("/classifier", json=json_post_dict) - assert response.status_code == 200 - assert ( - response.json()["classification_code"] - == BENIGN_GROWTH_BRAIN_CLASSIFICATION["classification_code"] - ) - assert ( - response.json()["classification_name"] - == BENIGN_GROWTH_BRAIN_CLASSIFICATION["classification_name"] - ) - - -def test_v3_table_diagnostic_code(client: TestClient): - json_post_dict = { - "diagnostic_code": DRUG_INDUCED_PULMONARY_PNEMONIA_CLASSIFICATION[ - "diagnostic_code" - ], - "claim_id": 123, - "form526_submission_id": 456, - } - - response = client.post("/classifier", json=json_post_dict) - assert response.status_code == 200 - assert ( - response.json()["classification_code"] - == DRUG_INDUCED_PULMONARY_PNEMONIA_CLASSIFICATION["classification_code"] - ) - assert ( - response.json()["classification_name"] - == DRUG_INDUCED_PULMONARY_PNEMONIA_CLASSIFICATION["classification_name"] - ) - - -def test_v4_table_diagnostic_code(client: TestClient): - json_post_dict = { - "diagnostic_code": 7301, - "claim_id": 123, - "form526_submission_id": 456, - } - - response = client.post("/classifier", json=json_post_dict) - assert response.status_code == 200 - assert response.json()["classification_code"] == 8920 - assert response.json()["classification_name"] == "Adhesions - Digestive" - - -def test_v5_diagnostic_code(client: TestClient): - json_post_dict = { - "diagnostic_code": 5255, - "claim_id": 100, - "form526_submission_id": 500, - "contention_text": "uncovered", - "claim_type": "claim_for_increase", - } - response = client.post("/classifier", json=json_post_dict) - assert response.status_code == 200 - assert ( - response.json()["classification_code"] - == IMPAIRMENT_OF_FEMUR_CLASSIFICATION["classification_code"] - ) - assert ( - response.json()["classification_name"] - == IMPAIRMENT_OF_FEMUR_CLASSIFICATION["classification_name"] - ) diff --git a/domain-cc/cc-app/tests/test_claim_linker.py b/domain-cc/cc-app/tests/test_claim_linker.py index ec50f15e02..29b7fb9198 100644 --- a/domain-cc/cc-app/tests/test_claim_linker.py +++ b/domain-cc/cc-app/tests/test_claim_linker.py @@ -1,3 +1,5 @@ +import logging + from fastapi.testclient import TestClient @@ -6,7 +8,11 @@ def test_claim_ids_logged(client: TestClient, caplog): "va_gov_claim_id": 100, "vbms_claim_id": 200, } - client.post("/claim-linker", json=json_post_dict) + + with caplog.at_level(logging.INFO): + client.post("/claim-linker", json=json_post_dict) + + print(caplog.records) expected_claim_link_json = { "level": "info", "message": "linking claims", diff --git a/domain-cc/cc-app/tests/test_condition_dropdown_lookup.py b/domain-cc/cc-app/tests/test_condition_dropdown_lookup.py deleted file mode 100644 index 51a3520abd..0000000000 --- a/domain-cc/cc-app/tests/test_condition_dropdown_lookup.py +++ /dev/null @@ -1,173 +0,0 @@ -""" Tests for the addition of new condition dropdown mappings to the /classifier endpoint """ - -from fastapi.testclient import TestClient - -from .conftest import TUBERCULOSIS_CLASSIFICATION - - -def test_diagnostic_code_mapping(client: TestClient): - """diagnostic code mapping still works the same as v1""" - json_post_dict = { - "diagnostic_code": TUBERCULOSIS_CLASSIFICATION["diagnostic_code"], - "claim_id": 100, - "form526_submission_id": 500, - "contention_text": "this_is_a_test", - "claim_type": "claim_for_increase", - } - - response = client.post("/classifier", json=json_post_dict) - assert response.status_code == 200 - assert ( - response.json()["classification_code"] - == TUBERCULOSIS_CLASSIFICATION["classification_code"] - ) - - -def test_classification_dropdown_cfi(client: TestClient): - """classifier will fall back to dropdown lookup table if diagnostic code is not found""" - json_post_dict = { - "diagnostic_code": 999999999, - "claim_id": 100, - "form526_submission_id": 500, - "contention_text": "Tuberculosis", - "claim_type": "claim_for_increase", - } - - response = client.post("/classifier", json=json_post_dict) - assert response.status_code == 200 - assert ( - response.json()["classification_code"] - == TUBERCULOSIS_CLASSIFICATION["classification_code"] - ) - - -def test_dropdown_lut_case_insensitive(client: TestClient): - """dropdown lookup table is case insensitive""" - json_post_dict = { - "claim_id": 700, - "form526_submission_id": 777, - "claim_type": "new", - "contention_text": "tUbeRcUloSis", - } - - response = client.post("/classifier", json=json_post_dict) - assert response.status_code == 200 - assert ( - response.json()["classification_code"] - == TUBERCULOSIS_CLASSIFICATION["classification_code"] - ) - - -def test_dropdown_lut_whitespace(client: TestClient): - """dropdown lookup table doesn't care about whitespace""" - json_post_dict = { - "claim_id": 700, - "form526_submission_id": 777, - "claim_type": "new", - "contention_text": " tuberculosis ", - } - - response = client.post("/classifier", json=json_post_dict) - assert response.status_code == 200 - assert ( - response.json()["classification_code"] - == TUBERCULOSIS_CLASSIFICATION["classification_code"] - ) - - -def test_classification_dropdown_new(client: TestClient): - json_post_dict = { - "claim_id": 100, - "form526_submission_id": 500, - "contention_text": "Tuberculosis", - "claim_type": "new", - } - - response = client.post("/classifier", json=json_post_dict) - assert response.status_code == 200 - assert ( - response.json()["classification_code"] - == TUBERCULOSIS_CLASSIFICATION["classification_code"] - ) - - -def test_null_response(client: TestClient): - """if both LUT's fail to map to classification, return null""" - json_post_dict = { - "diagnostic_code": 7, - "claim_id": 700, - "form526_submission_id": 777, - "claim_type": "claim_for_increase", - "contention_text": "this_is_a_test", - } - - response = client.post("/classifier", json=json_post_dict) - assert response.status_code == 200 - assert response.json() is None - - -def test_new_dropdown_list(client: TestClient): - """Test that a null response is returned if the v2 dropdown list is not specified""" - json_post_dict = { - "diagnostic_code": 7, - "claim_id": 700, - "form526_submission_id": 777, - "claim_type": "new", - "contention_text": "migraines (headaches)", - } - response = client.post("/classifier", json=json_post_dict) - assert response.status_code == 200 - assert response.json()["classification_code"] == 9007 - assert response.json()["classification_name"] == "Neurological other System" - - -def test_whitespace_removal(client: TestClient): - json_post_dict = { - "diagnostic_code": 7, - "claim_id": 700, - "form526_submission_id": 777, - "claim_type": "new", - "contention_text": "pharyngeal cancer (throat cancer) ", - } - response = client.post("/classifier", json=json_post_dict) - assert response.status_code == 200 - assert response.json()["classification_code"] == 1230 - assert response.json()["classification_name"] == "Cancer - Respiratory" - - -def test_v5_v6_lookup_values(client: TestClient): - """ - This tests new classification mappings in v5 of the condition dropdown list - and v6 of the diagnostic code lookup tables. - """ - json_post_dict = { - "claim_id": 100, - "form526_submission_id": 500, - "contentions": [ - { - "contention_text": "PTSD (post-traumatic stress disorder)", - "contention_type": "NEW", - }, - { - "contention_text": "", - "contention_type": "INCREASE", - "diagnostic_code": 5012, - }, - ], - } - response = client.post("/va-gov-claim-classifier", json=json_post_dict) - expected_classifications = [ - {"classification_code": 8989, "classification_name": "Mental Disorders"}, - { - "classification_code": 8940, - "classification_name": "Cancer - Musculoskeletal - Other", - }, - ] - returned_classifications = [ - { - "classification_code": c["classification_code"], - "classification_name": c["classification_name"], - } - for c in response.json()["contentions"] - ] - assert expected_classifications == returned_classifications diff --git a/domain-cc/cc-app/tests/test_multicontention_claims.py b/domain-cc/cc-app/tests/test_multicontention_claims.py index f4db0bf1f5..48724a6569 100644 --- a/domain-cc/cc-app/tests/test_multicontention_claims.py +++ b/domain-cc/cc-app/tests/test_multicontention_claims.py @@ -62,6 +62,32 @@ def test_vagov_classifier_empty_contentions(client: TestClient): assert response.status_code == 422 +def test_vagov_classifier_missing_params(client: TestClient): + """ + Tests 422 is returned when contentions is empty + """ + json_post_dict = { + "contentions": [ + { + "contention_text": "tinnitus (ringing or hissing in ears)", + "contention_type": "NEW", + }, + { + "contention_text": "asthma", + "contention_type": "INCREASE", + "diagnostic_code": 8550, + }, + { + "contention_text": "free text entry", + "contention_type": "NEW", + "diagnostic_code": None, + }, + ], + } + response = client.post("/va-gov-claim-classifier", json=json_post_dict) + assert response.status_code == 422 + + def test_single_contention(client: TestClient): """ Tests response of single contention claim matches expected values @@ -120,3 +146,112 @@ def test_order_response(client: TestClient): response.json()["contentions"][i]["classification_code"] == expected_order[i] ) + + +def test_case_insensitivity(client: TestClient): + """ + Tests that the classifier is case insensitive + """ + json_post_dict = { + "claim_id": 100, + "form526_submission_id": 500, + "contentions": [ + { + "contention_text": "TINNITUS (ringing or hissing in ears)", + "contention_type": "NEW", + }, + { + "contention_text": "ASTHMA", + "contention_type": "INCREASE", + "diagnostic_code": 8550, + }, + { + "contention_text": "FREE TEXT ENTRY", + "contention_type": "NEW", + "diagnostic_code": None, + }, + ], + } + response = client.post("/va-gov-claim-classifier", json=json_post_dict) + assert response.status_code == 200 + assert response.json()["contentions"] == [ + { + "classification_code": 3140, + "classification_name": "Hearing Loss", + "diagnostic_code": None, + "contention_type": "NEW", + }, + { + "classification_code": 9012, + "classification_name": "Respiratory", + "diagnostic_code": 8550, + "contention_type": "INCREASE", + }, + { + "classification_code": None, + "classification_name": None, + "diagnostic_code": None, + "contention_type": "NEW", + }, + ] + + +def test_whitespace_removal(client: TestClient): + json_post_dict = { + "claim_id": 100, + "form526_submission_id": 500, + "contentions": [ + { + "contention_text": " tinnitus (ringing or hissing in ears) ", + "contention_type": "NEW", + } + ], + } + response = client.post("/va-gov-claim-classifier", json=json_post_dict) + assert response.status_code == 200 + assert response.json()["contentions"] == [ + { + "classification_code": 3140, + "classification_name": "Hearing Loss", + "diagnostic_code": None, + "contention_type": "NEW", + } + ] + + +def test_v5_v6_lookup_values(client: TestClient): + """ + This tests new classification mappings in v5 of the condition dropdown list + and v6 of the diagnostic code lookup tables. + """ + json_post_dict = { + "claim_id": 100, + "form526_submission_id": 500, + "contentions": [ + { + "contention_text": "PTSD (post-traumatic stress disorder)", + "contention_type": "NEW", + }, + { + "contention_text": "", + "contention_type": "INCREASE", + "diagnostic_code": 5012, + }, + ], + } + response = client.post("/va-gov-claim-classifier", json=json_post_dict) + expected_classifications = [ + {"classification_code": 8989, "classification_name": "Mental Disorders"}, + { + "classification_code": 8940, + "classification_name": "Cancer - Musculoskeletal - Other", + }, + ] + returned_classifications = [ + { + "classification_code": c["classification_code"], + "classification_name": c["classification_name"], + } + for c in response.json()["contentions"] + ] + assert expected_classifications == returned_classifications