From 5d196843da102fdca0a6485a64b64f89d50431b2 Mon Sep 17 00:00:00 2001 From: Mayank Aamseek Date: Thu, 11 Sep 2025 13:31:03 +0000 Subject: [PATCH] Merged in bugfix/trigger_cap_fixes (pull request #704) Bugfix/trigger cap fixes * stoploss prompt added again * stoploss prompt added again * stoploss prompt updated * reverted back to one stage for trigger cap cases * Merge branch 'main' into bugfix/trigger_cap_fixes * pipeline error fixed * incorrect discount special case fixed Approved-by: Alex Galarce --- .../constants/mappings/valid_special.json | 2 +- .../constants/special_case_configs.py | 2 +- fieldExtraction/tests/test_one_to_n.py | 30 +++++++++---------- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/fieldExtraction/constants/mappings/valid_special.json b/fieldExtraction/constants/mappings/valid_special.json index eedbd0d..7ec671b 100644 --- a/fieldExtraction/constants/mappings/valid_special.json +++ b/fieldExtraction/constants/mappings/valid_special.json @@ -6,7 +6,7 @@ "OUTLIER": "Describes a payment methodology that provides additional supplemental reimbursement on top of base payments when claims exceed predetermined cost, day, or resource thresholds for INPATIENT SERVICES ONLY. These payments ADD to (not replace) standard reimbursement and are triggered automatically for unusually high-cost or high-resource inpatient cases. May be referenced as 'outlier payments', 'high-cost adjustments', 'extended stay supplements'. CRITICAL EXCLUSION: DOES NOT INCLUDE stop-loss provisions, stop loss arrangements, risk protection mechanisms, loss limitation clauses, or any similar risk-sharing provisions - even if they involve additional payments for high-cost cases, these are contractual risk protections, NOT outlier payments. Outlier payments apply exclusively to inpatient services and do not apply to outpatient, ambulatory, or other non-inpatient care settings.", "STOP_LOSS": "Describes financial safety net provisions primarily designed to protect providers against extreme losses. May apply at individual claim or aggregate levels and typically has higher thresholds than standard outlier provisions. Look for provisions specifically labeled as 'Stop Loss' that function separately from the standard payment methodology. (e.g. compensate Provider 120% of the published Medicaid Rate once claim exceeds a payable amount of $300,000", "SEQUESTRATION": "Describes a government-mandated reduction in federal spending for a period of time. Look for the term 'Sequestration'.", - "DISCOUNT": "Describes a discount to the payment. Look for the term 'Discount'.", + "DISCOUNT": "Describes a discount applied to the payment. The description must include the term 'Discount' and should specify the discount amount, percentage, or type.", "PREMIUM": "Describes a premium to the payment. Look for the term 'Premium'.", "ADDITION": "Describes a payment where an additional amount is added to the base reimbursement rate for individual claims. This can be a fixed dollar amount, a percentage of the base reimbursement, or a variable amount derived from the base rate that may change over time based on specified conditions, schedules, or specific services or procedures. Exclude cases where: \n- No specific amount or percentage tied to the base reimbursement of an individual claim is stated, \n- It is unclear whether the payment applies to individual claims, or \n- The payment is dependent on total compensation to Participating IPA Providers.", "TRIGGER_CAP": "Describes the payments that trigger a complete shift to a new payment methodology after a defined quantitative threshold is reached. Unlike supplemental payments, this fundamentally changes how reimbursement is calculated beyond the threshold. Valid thresholds can be based on dollar amounts, number of days, visits, units, or other measurable metrics. However, the following scenarios are NOT considered trigger caps: (1) Payment variations based solely on the timing of an event, such as discharge or admission dates (e.g., different payment if discharge occurs before or after a certain day of the month); (2) Thresholds based on the number of births (e.g., 'If the number of deliveries exceeds 17 per 1,000 members, an additional payment of $2,193 per birth will apply'); (3) Administrative pricing mechanisms such as Charge Description Master (CDM) increases or decreases; (4) Reimbursement limits tied to Acquisition Cost validations (e.g., payments capped at cost plus a percentage); (5) Provisions labeled as 'Stop-Loss' in either the service description or reimbursement text — even if a threshold and rate change are present — as these are considered risk protection mechanisms rather than true shifts in payment methodology; 6) Conditional changes in payment based on service context (e.g., ER vs. admission, Observation vs. surgery) without a numeric threshold do not qualify or Conditional reimbursement rules based on service combinations. 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', 'above X, reimburse at Y%', or 'over X threshold, payment becomes Y','when charges exceed $50,000, pay 80%' or 'after 3 days, payment changes to $1,200 per diem'.", diff --git a/fieldExtraction/constants/special_case_configs.py b/fieldExtraction/constants/special_case_configs.py index cf68eaa..389efc5 100644 --- a/fieldExtraction/constants/special_case_configs.py +++ b/fieldExtraction/constants/special_case_configs.py @@ -237,7 +237,7 @@ SPECIAL_CASE_CONFIGS = { "carveout_ind": False, "default_ind": False, "term_key": "methodology", - "use_two_stage": True, # Indicates special two-stage processing in which this case gets its own breakout separate from the main methodology breakout + "use_two_stage": False, }, "RATE_ESCALATOR": { "methodology_prompt_template": prompt_templates.REIMB_TERM_BREAKOUT, diff --git a/fieldExtraction/tests/test_one_to_n.py b/fieldExtraction/tests/test_one_to_n.py index 000bd7c..ee7b3d7 100644 --- a/fieldExtraction/tests/test_one_to_n.py +++ b/fieldExtraction/tests/test_one_to_n.py @@ -534,13 +534,12 @@ class TestOneToN(unittest.TestCase): result["OUTLIER_TERMS"], "Emergency Services - 110% of Medicare" ) - @patch("src.investment.one_to_n_funcs.methodology_breakout_single_row") - @patch("src.investment.one_to_n_funcs.process_special_cases") + @patch("src.investment.one_to_n_funcs.get_carveout_and_special_fields") @patch("src.utils.llm_utils.invoke_claude") - def test_get_special_cases_two_stage_trigger_cap( - self, mock_invoke_claude, mock_process_special_cases, mock_methodology_breakout + def test_get_special_cases_one_stage_trigger_cap( + self, mock_invoke_claude, mock_get_carveout_and_special_fields ): - """Test two-stage processing for TRIGGER_CAP""" + """Test one-stage processing for TRIGGER_CAP""" # Setup test_answers = [ { @@ -555,22 +554,21 @@ class TestOneToN(unittest.TestCase): "|PAYMENT_CARVEOUT|", # Carveout check ] - # Mock methodology breakout results - mock_methodology_breakout.return_value = [ + # Mock get_carveout_and_special_fields results + mock_get_carveout_and_special_fields.return_value = [ { "SERVICE_TERM": "High Cost Drugs - over $500", "REIMB_TERM": "lesser of 70% or fee schedule", "AARETE_DERIVED_REIMB_METHOD": "Lesser Of", "REIMB_PCT_RATE": "70%", + "CARVEOUT_CD": "PAYMENT_CARVEOUT", + "CARVEOUT_IND": "Y", + "DEFAULT_IND": "N", + "TRIGGER_BASE_THRESHOLD": "$500", + "TRIGGER_CAP_THRESHOLD_AMT": "70% of billed charges", } ] - # Mock special case results - mock_process_special_cases.return_value = { - "TRIGGER_BASE_THRESHOLD": "$500", - "TRIGGER_CAP_THRESHOLD_AMT": "70% of billed charges", - } - # Execute with patch( "constants.special_case_configs.CARVEOUT_CONFIGS", @@ -582,15 +580,15 @@ class TestOneToN(unittest.TestCase): # Assert self.assertEqual(len(result), 1) + self.assertEqual(result[0]["CARVEOUT_CD"], "PAYMENT_CARVEOUT") self.assertEqual(result[0]["CARVEOUT_IND"], "Y") self.assertEqual(result[0]["DEFAULT_IND"], "N") self.assertEqual(result[0]["TRIGGER_BASE_THRESHOLD"], "$500") self.assertEqual(result[0]["AARETE_DERIVED_REIMB_METHOD"], "Lesser Of") - # Verify two-stage functions were called - mock_methodology_breakout.assert_called_once() - mock_process_special_cases.assert_called_once() + # Verify one-stage function was called + mock_get_carveout_and_special_fields.assert_called_once() @patch("src.investment.one_to_n_funcs.methodology_breakout_single_row") def test_get_carveout_and_special_fields_unrecognized_carveout(