Merged in bugfix/prov_info_json_fixes (pull request #981)
fix: fall back to PROVIDER_NAME in PROV_INFO_JSON when no TIN/NPI extracted * fix: fall back to PROVIDER_NAME in PROV_INFO_JSON when no TIN/NPI extracted When get_prov_info_json short-circuits due to no TIN/NPI regex matches, PROV_INFO_JSON was left as [] even when PROVIDER_NAME was successfully extracted via the one-to-one pipeline. This caused inconsistent output across contracts with the same provider — some files produced a NAME-only entry (via a false-positive regex hit triggering the LLM), others produced []. Reconcile at add_group_and_other, the first point where both extraction streams' results are available. When PROV_INFO_JSON is empty but PROVIDER_NAME is known, synthesize a NAME-only entry with IS_GROUP:"Y" and populate PROV_GROUP_NAME_FULL directly — skipping the provider_name_match_check LLM call since the match is tautological by construction. Adds 5 unit tests covering the str, list, already-populated, empty-name, and all-empty-list cases. Approved-by: Katon Minhas
This commit is contained in:
committed by
Katon Minhas
parent
3ccd901743
commit
da37f694ec
@@ -344,6 +344,36 @@ def add_group_and_other(one_to_one_results: dict[str, Any], filename: str):
|
||||
provider_name = one_to_one_results.get("PROVIDER_NAME") # Can be List or string
|
||||
prov_info_json = one_to_one_results["PROV_INFO_JSON"]
|
||||
|
||||
# Fallback when no TIN/NPI identifiers are present in the contract text:
|
||||
# the regex gate in get_prov_info_json short-circuits the LLM call, leaving
|
||||
# PROV_INFO_JSON empty even when PROVIDER_NAME was successfully extracted.
|
||||
# Synthesize NAME-only entries so the field consistently represents the
|
||||
# provider. By construction these entries are the group provider, so set
|
||||
# IS_GROUP and the group_*/other_* fields directly and return early — this
|
||||
# avoids an unnecessary provider_name_match_check LLM call.
|
||||
if not prov_info_json and not string_utils.is_empty(provider_name):
|
||||
names = provider_name if isinstance(provider_name, list) else [provider_name]
|
||||
valid_names = [str(n).strip() for n in names if not string_utils.is_empty(n)]
|
||||
if valid_names:
|
||||
for name in valid_names:
|
||||
prov_info_json.append(
|
||||
{
|
||||
"TIN": "N/A",
|
||||
"NPI": "N/A",
|
||||
"NAME": name,
|
||||
"FROM_FILENAME": "N",
|
||||
"IS_GROUP": "Y",
|
||||
}
|
||||
)
|
||||
one_to_one_results["PROV_INFO_JSON"] = prov_info_json
|
||||
one_to_one_results["PROV_GROUP_TIN"] = []
|
||||
one_to_one_results["PROV_GROUP_NPI"] = []
|
||||
one_to_one_results["PROV_GROUP_NAME_FULL"] = valid_names
|
||||
one_to_one_results["PROV_OTHER_TIN"] = []
|
||||
one_to_one_results["PROV_OTHER_NPI"] = []
|
||||
one_to_one_results["PROV_OTHER_NAME_FULL"] = []
|
||||
return one_to_one_results
|
||||
|
||||
group_tins, group_npis, group_names = set(), set(), set()
|
||||
other_tins, other_npis, other_names = set(), set(), set()
|
||||
|
||||
|
||||
@@ -336,3 +336,104 @@ class TestTinNpiFuncs:
|
||||
assert len(result["PROV_INFO_JSON"]) == 3
|
||||
# Sort both lists for comparison since order may not be deterministic
|
||||
assert sorted(result["FILENAME_TIN"]) == sorted(["123456789", "987654321"])
|
||||
|
||||
def test_add_group_fallback_when_empty_and_name_str(self):
|
||||
"""Empty PROV_INFO_JSON with a string PROVIDER_NAME synthesizes one entry."""
|
||||
one_to_one_results = {
|
||||
"PROV_INFO_JSON": [],
|
||||
"PROVIDER_NAME": "Oregon Health & Science University",
|
||||
}
|
||||
|
||||
result = tin_npi_funcs.add_group_and_other(one_to_one_results, "test.txt")
|
||||
|
||||
assert len(result["PROV_INFO_JSON"]) == 1
|
||||
entry = result["PROV_INFO_JSON"][0]
|
||||
assert entry == {
|
||||
"TIN": "N/A",
|
||||
"NPI": "N/A",
|
||||
"NAME": "Oregon Health & Science University",
|
||||
"FROM_FILENAME": "N",
|
||||
"IS_GROUP": "Y",
|
||||
}
|
||||
assert result["PROV_GROUP_NAME_FULL"] == ["Oregon Health & Science University"]
|
||||
assert result["PROV_GROUP_TIN"] == []
|
||||
assert result["PROV_GROUP_NPI"] == []
|
||||
assert result["PROV_OTHER_TIN"] == []
|
||||
assert result["PROV_OTHER_NPI"] == []
|
||||
assert result["PROV_OTHER_NAME_FULL"] == []
|
||||
|
||||
def test_add_group_fallback_when_empty_and_name_list(self):
|
||||
"""List PROVIDER_NAME produces one synthesized entry per non-empty name."""
|
||||
one_to_one_results = {
|
||||
"PROV_INFO_JSON": [],
|
||||
"PROVIDER_NAME": ["OHSU", "OHSU Home Infusion Pharmacy"],
|
||||
}
|
||||
|
||||
result = tin_npi_funcs.add_group_and_other(one_to_one_results, "test.txt")
|
||||
|
||||
assert len(result["PROV_INFO_JSON"]) == 2
|
||||
names = [e["NAME"] for e in result["PROV_INFO_JSON"]]
|
||||
assert names == ["OHSU", "OHSU Home Infusion Pharmacy"]
|
||||
for entry in result["PROV_INFO_JSON"]:
|
||||
assert entry["TIN"] == "N/A"
|
||||
assert entry["NPI"] == "N/A"
|
||||
assert entry["IS_GROUP"] == "Y"
|
||||
assert entry["FROM_FILENAME"] == "N"
|
||||
assert result["PROV_GROUP_NAME_FULL"] == [
|
||||
"OHSU",
|
||||
"OHSU Home Infusion Pharmacy",
|
||||
]
|
||||
|
||||
@patch(
|
||||
"src.pipelines.saas.prompts.prompt_calls.provider_name_match_check",
|
||||
return_value=True,
|
||||
)
|
||||
def test_add_group_no_fallback_when_prov_info_already_populated(self, _mock_check):
|
||||
"""When PROV_INFO_JSON already has entries, the fallback must not fire."""
|
||||
one_to_one_results = {
|
||||
"PROV_INFO_JSON": [
|
||||
{
|
||||
"TIN": "262998718",
|
||||
"NPI": "1073054714",
|
||||
"NAME": "OHSU",
|
||||
"FROM_FILENAME": "N",
|
||||
}
|
||||
],
|
||||
"PROVIDER_NAME": "OHSU",
|
||||
}
|
||||
|
||||
result = tin_npi_funcs.add_group_and_other(one_to_one_results, "test.txt")
|
||||
|
||||
# No synthesized entry: the single original entry remains, and the
|
||||
# normal loop processed it (IS_GROUP now set).
|
||||
assert len(result["PROV_INFO_JSON"]) == 1
|
||||
assert result["PROV_INFO_JSON"][0]["TIN"] == "262998718"
|
||||
assert result["PROV_INFO_JSON"][0]["NPI"] == "1073054714"
|
||||
assert result["PROV_INFO_JSON"][0]["IS_GROUP"] == "Y"
|
||||
|
||||
@pytest.mark.parametrize("empty_name", ["", None, "N/A", []])
|
||||
def test_add_group_no_fallback_when_name_empty(self, empty_name):
|
||||
"""Empty/None/N/A/[] PROVIDER_NAME leaves PROV_INFO_JSON empty."""
|
||||
one_to_one_results = {
|
||||
"PROV_INFO_JSON": [],
|
||||
"PROVIDER_NAME": empty_name,
|
||||
}
|
||||
|
||||
result = tin_npi_funcs.add_group_and_other(one_to_one_results, "test.txt")
|
||||
|
||||
assert result["PROV_INFO_JSON"] == []
|
||||
assert result["PROV_GROUP_NAME_FULL"] == []
|
||||
assert result["PROV_GROUP_TIN"] == []
|
||||
assert result["PROV_GROUP_NPI"] == []
|
||||
|
||||
def test_add_group_no_fallback_when_name_list_all_empty(self):
|
||||
"""List of only empty/N/A names leaves PROV_INFO_JSON empty."""
|
||||
one_to_one_results = {
|
||||
"PROV_INFO_JSON": [],
|
||||
"PROVIDER_NAME": ["", "N/A", None],
|
||||
}
|
||||
|
||||
result = tin_npi_funcs.add_group_and_other(one_to_one_results, "test.txt")
|
||||
|
||||
assert result["PROV_INFO_JSON"] == []
|
||||
assert result["PROV_GROUP_NAME_FULL"] == []
|
||||
|
||||
Reference in New Issue
Block a user