Merged in bugfix/DAIP2-2524-carveout-code-optimization (pull request #980)
CARVEOUT_CD issue fixed * CARVEOUT_CD issue fixed * pipeline error fixed * Merged dev into bugfix/DAIP2-2524-carveout-code-optimization * Merged dev into bugfix/DAIP2-2524-carveout-code-optimization * Merged dev into bugfix/DAIP2-2524-carveout-code-optimization * trigger cap issue fixed * trigger cap prompt updated * Merged dev into bugfix/DAIP2-2524-carveout-code-optimization Approved-by: Katon Minhas
This commit is contained in:
committed by
Katon Minhas
parent
f9464fc8c6
commit
7a39dabcb7
@@ -239,6 +239,7 @@ class Constants:
|
||||
.from_json(path=_path("constants/mappings/valid_carveouts.json"))
|
||||
.mapping
|
||||
)
|
||||
self.VALID_CARVEOUTS_LIST = list(self.VALID_CARVEOUTS.keys())
|
||||
|
||||
#################################### Vendor-Specific Lists ####################################
|
||||
self.CUSTOMER_CONTRACT_TYPE = (
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
"description": "Valid carveout types and descriptions"
|
||||
},
|
||||
"mapping": {
|
||||
"BASE_COVERED_SERVICES": "Base reimbursement rates that form the foundation of the payment structure. Describes foundational payment methodologies for standard covered services, including primary coverage determinations. This refers to payment methodologies that establish the fundamental reimbursement structure, not exceptions or adjustments to it. Examples: 'covered services' 'Covered Services not included...', 'services primary to Medicare', etc.",
|
||||
"PAYMENT_CARVEOUT": "Describes payments for specific services, procedures, level designations ('Secondary', 'Primary', 'Tertiary', etc), or specific DRG or codes or specific ages('Child','Adolescent','Adult', etc) that are carved out from the base/foundational payment methodology with distinct payment terms. These are exceptions or specialty payments that differ from the payments for standard/ base services. Examples include 'Radiology covered services', 'Anesthesia covered services', 'Pharmaceuticals', 'HEMATOCRIT (CPT CODE: 85014)', and level designations such as 'Secondary', 'Primary', 'Tertiary', 'Progressive Care'. Do NOT classify as payment carveout if the service represents a foundational coverage determination (such as primary payer methodologies in dual products) rather than a different payment to standard one.",
|
||||
"BASE_COVERED_SERVICES": "Base reimbursement rates that form the foundation of the payment structure. Describes foundational payment methodologies for standard covered services (NOT specific services or service types or codes), including primary coverage determinations. This refers to payment methodologies that establish the fundamental reimbursement structure, not exceptions or adjustments to it. Examples: 'covered services' 'Covered Services not included...', 'services primary to Medicare', etc.",
|
||||
"PAYMENT_CARVEOUT": "Describes payments for specific services, procedures, revenue codes, level designations ('Secondary', 'Primary', 'Tertiary', etc), or specific DRG or codes or specific ages('Child','Adolescent','Adult', etc) that are carved out from the base/foundational payment methodology with distinct payment terms. These are exceptions or specialty payments that differ from the payments for standard/ base services. Examples include 'Radiology covered services', 'Anesthesia covered services', 'Pharmaceuticals', 'HEMATOCRIT (CPT CODE: 85014)', and level designations such as 'Secondary', 'Primary', 'Tertiary', 'Progressive Care'. Do NOT classify as payment carveout if the service represents a foundational coverage determination (such as primary payer methodologies in dual products) rather than a different payment to standard one.",
|
||||
"UNLISTED_CODE": "Describes payments for codes that are not listed on a fee schedule. This is similar to DEFAULT_TERM. Only populate with Y if the Service or Methodology specifically mentions Codes.",
|
||||
"ADDITION": "Describes payments that include a supplemental amount added to or above the base reimbursement rate. Populate with Y if the Methodology describes any additional payments, supplemental amounts, or add-on fees beyond the standard reimbursement.",
|
||||
"TRIGGER_CAP": "Describes a new payment methodology which becomes applicable/ gets triggered after a defined quantitative threshold is reached. Unlike supplemental payments, this refers to fundamentally changed new reimbursement methodology applicable only beyond the threshold. Valid thresholds can be based on dollar amounts, number of days, visits, units, or other measurable metrics (but not qualitative thresholds, e.g., 'where there is no Marketplace Medicare Rate'). Look for language indicating a clear change in the payment methodology when a defined quantitative threshold is exceeded, such as 'when charges exceed X, pay Y', or 'after 3 days, payment changes to $1,200 per diem'. Do NOT classify as TRIGGER_CAP if the term better fits the criteria for Outlier, Stop-Loss, or simple thresholds.",
|
||||
"TRIGGER_CAP": "Describes a new payment methodology which becomes applicable after a defined quantitative threshold is reached. Unlike supplemental payments, this refers to a fundamentally changed reimbursement methodology applicable only beyond the threshold. Valid thresholds can be based on dollar amounts, number of days, visits, units, or other measurable metrics (not qualitative thresholds, e.g., 'where there is no Marketplace Medicare Rate'). A true TRIGGER_CAP fundamentally switches the payment method at the threshold — e.g., 'when charges exceed X, pay Y', or 'after 3 days, payment changes to $1,200 per diem'. Do NOT classify as TRIGGER_CAP if the term better fits Outlier, Stop-Loss, or simple thresholds, or if the threshold merely caps or limits an existing payment method rather than replacing it with a new one.",
|
||||
"NEVER_EVENT": "Indicates that Never Events (e.g. sepsis acquired while inpatient) will not be covered. This may be phrased as 'no liability' or 'no payment'.",
|
||||
"EXPERIMENTAL_INVESTIGATIONAL": "Describes payments for experimental and investigational procedures.",
|
||||
"NOT_MEDICALLY_NECESSARY": "Describes payments for procedures deemed not medically necessary (or medically unnecessary)",
|
||||
|
||||
@@ -266,22 +266,6 @@ def process_single_carveout(
|
||||
)
|
||||
|
||||
if reimbursement_categorization in carveout_definitions.keys():
|
||||
answer_dict["CARVEOUT_CD"] = reimbursement_categorization
|
||||
# Standard N/A OR Base response gets CARVEOUT_IND=N
|
||||
if reimbursement_categorization in [
|
||||
"N/A",
|
||||
"BASE_COVERED_SERVICES",
|
||||
"DEFAULT_TERM",
|
||||
]:
|
||||
answer_dict["CARVEOUT_IND"] = "N"
|
||||
else:
|
||||
answer_dict["CARVEOUT_IND"] = "Y"
|
||||
# Determine DEFAULT_IND based on specific carveout_answer values
|
||||
answer_dict["DEFAULT_IND"] = (
|
||||
"Y"
|
||||
if reimbursement_categorization in ["DEFAULT_TERM", "UNLISTED_CODE"]
|
||||
else "N"
|
||||
)
|
||||
|
||||
# Carveout breakouts
|
||||
if reimbursement_categorization == "TRIGGER_CAP":
|
||||
@@ -855,6 +839,31 @@ def get_lob_relationship(answer_dicts, exhibit_text, filename):
|
||||
return updated_dicts
|
||||
|
||||
|
||||
def set_carveout_indicators(all_exhibit_rows: list[dict]) -> list[dict]:
|
||||
"""
|
||||
Sets CARVEOUT_IND and DEFAULT_IND based on CARVEOUT_CD values.
|
||||
|
||||
Args:
|
||||
all_exhibit_rows: List of dictionaries containing reimbursement records.
|
||||
|
||||
Returns:
|
||||
Updated list with CARVEOUT_IND and DEFAULT_IND populated.
|
||||
"""
|
||||
for row in all_exhibit_rows:
|
||||
carveout_cd = row.get("CARVEOUT_CD", "")
|
||||
# Standard N/A OR Base response gets CARVEOUT_IND=N
|
||||
if carveout_cd in ["N/A", "BASE_COVERED_SERVICES", "DEFAULT_TERM"]:
|
||||
row["CARVEOUT_IND"] = "N"
|
||||
else:
|
||||
row["CARVEOUT_IND"] = "Y"
|
||||
|
||||
row["DEFAULT_IND"] = (
|
||||
"Y" if carveout_cd in ["DEFAULT_TERM", "UNLISTED_CODE"] else "N"
|
||||
)
|
||||
|
||||
return all_exhibit_rows
|
||||
|
||||
|
||||
def one_to_n_cleaning(
|
||||
all_exhibit_rows: list[dict], exhibit_text: str, constants: Constants, filename: str
|
||||
):
|
||||
@@ -886,6 +895,12 @@ def one_to_n_cleaning(
|
||||
):
|
||||
all_exhibit_rows = split_reimb_dates(all_exhibit_rows, filename)
|
||||
|
||||
################################ Set Carveout Indicators ################################
|
||||
with timing_utils.timed_block(
|
||||
"one_to_n_cleaning.set_carveout_indicators", context=filename
|
||||
):
|
||||
all_exhibit_rows = set_carveout_indicators(all_exhibit_rows)
|
||||
|
||||
################################ Normalize Single-Value Fields to Strings ################################
|
||||
# REIMB_TERM, SERVICE_TERM should always be strings (not lists)
|
||||
# LOB_PROGRAM_RELATIONSHIP and LOB_PRODUCT_RELATIONSHIP are already normalized to strings
|
||||
|
||||
@@ -61,6 +61,13 @@
|
||||
"field_type": "methodology_breakout",
|
||||
"prompt": "If the methodology text indicates that the reimbursement shall be the greater of two or more values, write 'Y' for this field. Otherwise, write 'N'."
|
||||
},
|
||||
{
|
||||
"field_name": "CARVEOUT_CD",
|
||||
"relationship": "one_to_n",
|
||||
"field_type": "methodology_breakout",
|
||||
"prompt": "Determine the carveout category for this reimbursement component. Choose only from the following valid values: {valid_values}. Never return 'N/A'.",
|
||||
"valid_values": "VALID_CARVEOUTS_LIST"
|
||||
},
|
||||
{
|
||||
"field_name": "REIMB_DATES",
|
||||
"relationship": "one_to_n",
|
||||
|
||||
@@ -993,6 +993,14 @@ def METHODOLOGY_BREAKOUT_INSTRUCTION() -> str:
|
||||
# Load fields from investment_prompts.json with resolved valid_values
|
||||
fields_text = _get_fields_text("methodology_breakout")
|
||||
|
||||
# Load carveout definitions from Constants
|
||||
constants = _get_constants()
|
||||
carveout_definitions = constants.VALID_CARVEOUTS
|
||||
|
||||
carveout_text = "\n".join(
|
||||
[f"{name} : {desc}" for name, desc in carveout_definitions.items()]
|
||||
)
|
||||
|
||||
return f"""[OBJECTIVE]
|
||||
Analyze a given term from a Payer-Provider contract and extract key fields.
|
||||
|
||||
@@ -1005,6 +1013,7 @@ Analyze a given term from a Payer-Provider contract and extract key fields.
|
||||
- **Do not skip any clause**. Every item compared under "lesser of", "greater of", "equal to", or similar comparative logic must be represented as its own dictionary in the output list.
|
||||
- If the comparative structure includes nested comparisons (e.g., "the lesser of [the greater of A or B] and C"), each one of A,B and C must be extracted as its own dictionary. LESSER_OF and GREATER_OF are determined only by the immediate parent comparison. So even if it is part of outer LESSER_OF comparison, LESSER_OF should be "N" if GREATER_OF is "Y". In the case of the above example, dictionaries A and B should have GREATER_OF as "Y" and LESSER_OF as "N". Dictionary C should have LESSER_OF as "Y" and GREATER_OF as "N".
|
||||
- **IMPORTANT**: For each component, classify the `AARETE_DERIVED_REIMB_METHOD` based on the INDIVIDUAL component, not the overall structure.
|
||||
- **IMPORTANT**: For each component, classify the `CARVEOUT_CD` based on the INDIVIDUAL component, not the overall structure. Use the carveout definitions to determine the appropriate category for each reimbursement method.
|
||||
- If the methodology includes **additional reimbursement terms** beyond the "lesser of" structure (e.g., per diem charges, or carve-out payments):
|
||||
- **These must also be extracted as separate JSON dictionaries** in the same list.
|
||||
- These are often found in follow-on sentences or clauses and may not be part of a comparative limit but are still enforceable and reimbursable.
|
||||
@@ -1018,6 +1027,9 @@ If AARETE_DERIVED_REIMB_METHOD is Grouper, the dollar amount refers to REIMB_CON
|
||||
Populate a list of JSON dictionaries with the following fields:
|
||||
{fields_text}
|
||||
|
||||
[ADDITIONAL INFO TO BE USED FOR DETERMINING CARVEOUT_CD]
|
||||
{carveout_text}
|
||||
|
||||
[OUTPUT FORMAT]
|
||||
The output should always be a JSON list/array of dictionaries, even when there is only one methodology.
|
||||
{JSON_LIST_OF_DICTS_FORMAT_INSTRUCTIONS}
|
||||
|
||||
@@ -340,9 +340,6 @@ class TestOneToNFuncs(unittest.TestCase):
|
||||
|
||||
self.assertEqual(len(reimb_answers), 1)
|
||||
self.assertEqual(len(special_answers), 0)
|
||||
self.assertEqual(reimb_answers[0]["CARVEOUT_CD"], "BASE_COVERED_SERVICES")
|
||||
self.assertEqual(reimb_answers[0]["CARVEOUT_IND"], "N")
|
||||
self.assertEqual(reimb_answers[0]["DEFAULT_IND"], "N")
|
||||
|
||||
@patch(
|
||||
"src.pipelines.shared.extraction.one_to_n_funcs.prompt_calls.prompt_carveout_check"
|
||||
@@ -359,10 +356,6 @@ class TestOneToNFuncs(unittest.TestCase):
|
||||
input_answers, self.constants, self.filename
|
||||
)
|
||||
|
||||
self.assertEqual(reimb_answers[0]["CARVEOUT_CD"], "DEFAULT_TERM")
|
||||
self.assertEqual(reimb_answers[0]["CARVEOUT_IND"], "N")
|
||||
self.assertEqual(reimb_answers[0]["DEFAULT_IND"], "Y")
|
||||
|
||||
@patch(
|
||||
"src.pipelines.shared.extraction.one_to_n_funcs.prompt_calls.prompt_carveout_check"
|
||||
)
|
||||
@@ -387,9 +380,6 @@ class TestOneToNFuncs(unittest.TestCase):
|
||||
input_answers, self.constants, self.filename
|
||||
)
|
||||
|
||||
self.assertEqual(reimb_answers[0]["CARVEOUT_CD"], "TRIGGER_CAP")
|
||||
self.assertEqual(reimb_answers[0]["CARVEOUT_IND"], "Y")
|
||||
self.assertEqual(reimb_answers[0]["DEFAULT_IND"], "N")
|
||||
self.assertIn("TRIGGER_AMOUNT", reimb_answers[0])
|
||||
|
||||
@patch(
|
||||
|
||||
Reference in New Issue
Block a user