diff --git a/docs/docs/data-marts/hcc-recapture.md b/docs/docs/data-marts/hcc-recapture.md index 95f85af9c..e4930fbac 100644 --- a/docs/docs/data-marts/hcc-recapture.md +++ b/docs/docs/data-marts/hcc-recapture.md @@ -25,12 +25,12 @@ The type of gap closure if provided using the `gap_status` field. Here are the g | Gap Status | Definition | |------------|------------| -| closed using higher coefficient hcc in hierarchy group | An HCC in the same group was closed, but its coefficient is greater than the prior year HCC | +| closed - higher coefficient hcc in hierarchy group | An HCC in the same group was closed, but its coefficient is greater than the prior year HCC | | closed | The specific HCC in question has been observed in a risk adjustable claim during the collection year | -| closed using lower coefficient hcc in hierarchy group | An HCC in the same group was closed, but its coefficient is less than the prior year HCC | +| closed - lower coefficient hcc in hierarchy group | An HCC in the same group was closed, but its coefficient is less than the prior year HCC | | new | Defined as an HCC that has not been coded in the past 2 years | | open | For gaps and claims, it's a chronic condition appropriate for recapture that has not been documented in current collection year | -| inappropriate for recapture | The specific HCC in question is "Open" and no related/equivalent HCC has been closed, but it is not appropriate for risk adjustment because it's not a chronic diagnosis | +| ineligible for recapture | The specific HCC in question is "Open" and no related/equivalent HCC has been closed, but it is not appropriate for risk adjustment because it's not a chronic diagnosis | Instead of just listing an HCC as closed, more detail is provided which presents an opportunity to improve future HCC recapture initiatives. @@ -39,3 +39,13 @@ When calculating HCC gap closure, YTD recapture curves are often used. Recapture All of the models below are the final models output from the HCC recapture data mart. +## Customizations +The following options are customizable in the HCC recapture mart to provide greater flexibility. + +### HCC Suspect Lists + +The `hcc_recapture_suspect_list` variable can be set to `true` in the `dbt_project.yml` in order to provide your own HCC suspect list from a payer or clinical source. The data needs to be input into a model called `suspect_hccs`. The required fields can be found in the `hcc_recapture__stg_suspect_hccs` model. + +### Chronic HCCs + +The `hcc_recapture_chronic_hccs` variable can be set to `true` in the `dbt_project.yml` in order to provide your own custom chronic HCC definition instead of using the CMS chronic HCC definitions already provided. The data needs to be input into a model called `chronic_hccs`. The required fields can be found in the `hcc_recapture__stg_chronic_hccs` model. \ No newline at end of file diff --git a/models/data_marts/cms_hcc/cms_hcc_models.yml b/models/data_marts/cms_hcc/cms_hcc_models.yml index b888591d0..ba38a5434 100644 --- a/models/data_marts/cms_hcc/cms_hcc_models.yml +++ b/models/data_marts/cms_hcc/cms_hcc_models.yml @@ -262,7 +262,7 @@ models: schema: | {%- if var('tuva_schema_prefix',None) != None -%}{{var('tuva_schema_prefix')}}_cms_hcc{% else %}cms_hcc{%- endif -%} alias: _int_demographic_factors - tags: cms_hcc + tags: ["cms_hcc", "hcc_recapture"] materialized: table description: > Demographic and enrollment risk relative factor values for the corresponding HCC model version and payment year. diff --git a/models/data_marts/hcc_recapture/final/hcc_recapture__gap_status.sql b/models/data_marts/hcc_recapture/final/hcc_recapture__gap_status.sql index ea0b4a2a2..3bb23703d 100644 --- a/models/data_marts/hcc_recapture/final/hcc_recapture__gap_status.sql +++ b/models/data_marts/hcc_recapture/final/hcc_recapture__gap_status.sql @@ -11,9 +11,11 @@ select distinct , risk_model_code , model_version , payment_year - , recapture_flag + , recapturable_flag + , hcc_type + , hcc_source , gap_status , suspect_hcc_flag -from {{ ref('hcc_recapture__int_gap_status') }} +from {{ ref('hcc_recapture__int_gap_status')}} -- Apply hierarchies -where filtered_out_by_hierarchy = 0 +where filtered_by_hierarchy_flag = 0 diff --git a/models/data_marts/hcc_recapture/final/hcc_recapture__hcc_status.sql b/models/data_marts/hcc_recapture/final/hcc_recapture__hcc_status.sql index 2ec28a67b..306bcc6c7 100644 --- a/models/data_marts/hcc_recapture/final/hcc_recapture__hcc_status.sql +++ b/models/data_marts/hcc_recapture/final/hcc_recapture__hcc_status.sql @@ -4,34 +4,55 @@ }} select distinct - stg.person_id - , stg.payer - , stg.data_source - , coalesce(gap.payment_year, {{ date_part('year', 'recorded_date') }} + 1) as payment_year - , stg.recorded_date - , stg.claim_id - , stg.rendering_npi - , stg.model_version - , stg.hcc_code - , stg.hcc_description - , stg.hcc_hierarchy_group - , stg.hcc_hierarchy_group_rank - , stg.suspect_hcc_flag - , stg.risk_model_code - , stg.eligible_claim_indicator - , stg.eligible_bene - , stg.reason - , gap.gap_status - , gap.recapture_flag -from {{ ref('hcc_recapture__int_hccs') }} as stg -left outer join {{ ref('hcc_recapture__gap_status') }} as gap - on stg.person_id = gap.person_id - and stg.payer = gap.payer - and stg.model_version = gap.model_version - and stg.hcc_code = gap.hcc_code - and stg.suspect_hcc_flag = gap.suspect_hcc_flag - and (case - when gap.gap_status = 'open' then stg.collection_year + 2 - else stg.collection_year + 1 - end) = gap.payment_year -where eligible_bene = 1 + hccs.person_id + , hccs.payer + , hccs.data_source + , coalesce(gap.payment_year, {{ date_part('year', 'hccs.recorded_date') }} + 1) as payment_year + , hccs.recorded_date + , hccs.claim_id + , hccs.rendering_npi + , hccs.model_version + , hccs.hcc_code + , hccs.hcc_description + , hccs.hcc_hierarchy_group + , hccs.hcc_hierarchy_group_rank + , hccs.suspect_hcc_flag + -- Latest risk_model_code per person/year/model_version based on recorded_date + , first_value(hccs.risk_model_code) over ( + partition by + hccs.person_id, + coalesce(gap.payment_year, {{ date_part('year', 'hccs.recorded_date') }} + 1), + hccs.model_version + order by hccs.recorded_date desc + ) as risk_model_code + , hccs.hcc_type + , hccs.hcc_source + , coalesce(gap.gap_status,'ineligible for recapture') as gap_status + -- Filters that may lead to an 'ineligible for recapture' gap status + , hccs.hcc_chronic_flag + , hccs.recapturable_flag + , hccs.eligible_claim_flag + , hccs.eligible_bene_flag + , coalesce(gap.filtered_by_hierarchy_flag, recap.filtered_by_hierarchy_flag,0) as filtered_by_hierarchy_flag +from {{ ref('hcc_recapture__int_all_hccs') }} as hccs +left join {{ ref('hcc_recapture__int_recapturable_hccs') }} as recap + on hccs.person_id = recap.person_id + and hccs.hcc_code = recap.hcc_code + and hccs.data_source = recap.data_source + and hccs.payer = recap.payer + and hccs.model_version = recap.model_version + and hccs.collection_year = recap.collection_year + and hccs.hcc_hierarchy_group = recap.hcc_hierarchy_group + and coalesce(hccs.claim_id, '') = coalesce(recap.claim_id, '') +left join {{ ref('hcc_recapture__int_gap_status') }} as gap + on hccs.person_id = gap.person_id + and hccs.payer = gap.payer + and hccs.model_version = gap.model_version + and hccs.hcc_code = gap.hcc_code + -- For TUVA gaps, +2 is needed because we’re comparing collection year to payment year - we already need a +1 for that comparison, and an additional +1 to account for closure in the following year. + -- For suspect HCCs, we only apply +1, since it’s ok for those HCCs to close themselves within the same year (e.g., CY 2025 suspect list HCCs can be closed in CY 2025 based on claims rather than CY 2026). + and (case + when gap.gap_status = 'open' and hccs.hcc_type = 'coded' then hccs.collection_year + 2 + else hccs.collection_year + 1 + end) = gap.payment_year +where hccs.eligible_bene_flag = 1 \ No newline at end of file diff --git a/models/data_marts/hcc_recapture/final/hcc_recapture__recapture_rates_monthly.sql b/models/data_marts/hcc_recapture/final/hcc_recapture__recapture_rates_monthly.sql index 90a3e2e94..54c172b38 100644 --- a/models/data_marts/hcc_recapture/final/hcc_recapture__recapture_rates_monthly.sql +++ b/models/data_marts/hcc_recapture/final/hcc_recapture__recapture_rates_monthly.sql @@ -20,12 +20,14 @@ select distinct , model_version , hcc_code , gap_status - , recapture_flag + , recapturable_flag , row_number() over (partition by person_id, payer, payment_year, model_version, hcc_code order by recorded_date asc) as earliest_hcc_code -from {{ ref('hcc_recapture__hcc_status') }} -where gap_status not in ('inappropriate for recapture', 'new') - and gap_status is not null - and suspect_hcc_flag = 0 +from {{ ref('hcc_recapture__hcc_status')}} +where 1=1 + and gap_status not in ('ineligible for recapture', 'new') + and hcc_type in ('captured', 'coded') + and recapturable_flag = 1 + and filtered_by_hierarchy_flag = 0 ) , monthly_hcc_counts as ( diff --git a/models/data_marts/hcc_recapture/final_models.yml b/models/data_marts/hcc_recapture/final_models.yml index 2e35ada89..c85357641 100644 --- a/models/data_marts/hcc_recapture/final_models.yml +++ b/models/data_marts/hcc_recapture/final_models.yml @@ -10,9 +10,10 @@ models: alias: recapture_rates tags: ["hcc_recapture", "final"] materialized: table - tests: + data_tests: - dbt_utils.unique_combination_of_columns: - combination_of_columns: + arguments: + combination_of_columns: - payer - payment_year columns: @@ -31,6 +32,7 @@ models: description: The total number of HCCs that were open or closed in the payment year. Excludes new and inappropriate for recapture HCCs. - name: recapture_rate description: Closed HCCs divided by Total HCCs in the given payment year. + - name: hcc_recapture__recapture_rates_monthly description: HCC recapture rates by payment year month. config: @@ -40,9 +42,10 @@ models: alias: recapture_rates_monthly tags: ["hcc_recapture", "final"] materialized: table - tests: + data_tests: - dbt_utils.unique_combination_of_columns: - combination_of_columns: + arguments: + combination_of_columns: - payer - payment_year - payment_year_month @@ -66,6 +69,7 @@ models: description: The total number of HCCs that were open or closed in the payment month. Excludes new and inappropriate for recapture HCCs. - name: recapture_rate description: Closed HCCs divided by Total HCCs in the given payment month. + - name: hcc_recapture__recapture_rates_monthly_ytd description: HCC recapture rates by payment month year-to-date. config: @@ -75,9 +79,10 @@ models: alias: recapture_rates_monthly_ytd tags: ["hcc_recapture", "final"] materialized: table - tests: + data_tests: - dbt_utils.unique_combination_of_columns: - combination_of_columns: + arguments: + combination_of_columns: - payer - payment_year - payment_year_month @@ -109,6 +114,7 @@ models: description: The number of HCCs open and closed in a payment year. - name: ytd_recapture_rate description: The running total of closed HCCs divided by total HCCs in a payment year. + - name: hcc_recapture__hcc_status description: Combines claims data with HCC gap status. config: @@ -118,20 +124,23 @@ models: alias: hcc_status tags: ["hcc_recapture", "final"] materialized: table - tests: + data_tests: - dbt_utils.unique_combination_of_columns: - combination_of_columns: - - person_id - - payer - - data_source - - payment_year - - recorded_date - - claim_id - - hcc_code - - rendering_npi - - model_version - - hcc_hierarchy_group - - hcc_hierarchy_group_rank + arguments: + combination_of_columns: + - person_id + - payer + - data_source + - payment_year + - recorded_date + - claim_id + - hcc_code + - rendering_npi + - model_version + - hcc_hierarchy_group + - hcc_hierarchy_group_rank + - suspect_hcc_flag + - filtered_by_hierarchy_flag columns: - name: person_id description: A unique identifier for a person. @@ -141,8 +150,8 @@ models: description: The name of the data source origin. Filled in by the user in the input layer files. - name: payment_year description: > - This is the year that the HCC should be coded. Typically the collection year + 1, but for open HCCs it will be the collection year + 2. - To illustrate, if we have an HCC claim in 2023, but not in 2024, that means it is open in 2024. The payment year is 2024 + 1 = 2025. So + This is the year that the HCC should be coded. Typically the collection year + 1, but for open HCCs it will be the collection year + 2. + To illustrate, if we have an HCC claim in 2023, but not in 2024, that means it is open in 2024. The payment year is 2024 + 1 = 2025. So an open HCC from 2023 will have a payment year = 2025. - name: payment_year - name: recorded_date description: The date the claim was originally recorded. Based on admission date, but if null then filled in by claim start date followed by claim end date. @@ -152,9 +161,9 @@ models: description: The National Provider Identifier (NPI) of the physician who rendered services to the beneficiary. - name: model_version description: > - The CMS Medicare risk model version. This includes models such as v22,v24, and v28 which are documented in the - yearly rate announcement (e.g. https://www.cms.gov/files/document/2026-announcement.pdf) and on the CMS risk adjustment - website: https://www.cms.gov/medicare/payment/medicare-advantage-rates-statistics/risk-adjustment + The CMS Medicare risk model version. This includes models such as v22,v24, and v28 which are documented in the + yearly rate announcement (e.g. https://www.cms.gov/files/document/2026-announcement.pdf) and on the CMS risk adjustment + website: https://www.cms.gov/medicare/payment/medicare-advantage-rates-statistics/risk-adjustment - name: hcc_code description: The hierarchical condition category code. - name: hcc_description @@ -165,35 +174,56 @@ models: For example, in 2025, the hierarchies were stored in a file called `V28115H1.txt`. The file will end in an H. - name: hcc_hierarchy_group_rank description: > - The rank within the HCC hierarchy group. The lower the number, the higher the rank. When 2 HCCs within the same group are + The rank within the HCC hierarchy group. The lower the number, the higher the rank. When 2 HCCs within the same group are coded within the same collection year, the HCC with the lower rank will be chosen of the two for a given beneficiary. - name: risk_model_code description: > There are different coefficients depending on a few factors such as dual eligibility, new enrollee, institutional status, etc... This column provides acronyms based off of the CMS risk adjustment SAS code. For example, these can be found in the `C2824T2N_25.csv` file for - the 2025 CMS risk adjustment software. - - name: eligible_claim_indicator + the 2025 CMS risk adjustment software. + - name: eligible_claim_flag description: > Whether the claim is eligible for risk adjustment as determined based on a list of accepted CPT/HCPCs codes from CMS. These are available on the CMS risk adjustment website: https://www.cms.gov/medicare/payment/medicare-advantage-rates-statistics/risk-adjustment - - name: eligible_bene + - name: eligible_bene_flag description: A 1 is indicated for a beneficiary who is in the eligibility files. A 0 is indicated if they are not in the eligibility files. - - name: reason - description: Free text reason for the appointment or service. + - name: hcc_chronic_flag + description: Whether the HCC is considered chronic or not. A 1 indicates the HCC is chronic. - name: gap_status description: > definitions for gap_status: - - 'closed using higher coefficient hcc in hierarchy group': An HCC in the same group was closed, but its coefficient is greater than the prior year HCC + - 'closed - higher coefficient hcc in hierarchy group': An HCC in the same group was closed, but its coefficient is greater than the prior year HCC - 'closed': the specific HCC in question has been observed in a risk adjustable claim during the collection year. - - 'closed using lower coefficient hcc in hierarchy group': An HCC in the same group was closed, but its coefficient is less than the prior year HCC + - 'closed - lower coefficient hcc in hierarchy group': An HCC in the same group was closed, but its coefficient is less than the prior year HCC - 'new': defined as an hcc that has not been coded in the past 2 years - 'open': for gaps and claims, it's a chronic condition appropriate for recapture that has not been documented in current collection year - - 'inappropriate for recapture': the specific HCC in question is “Open” and no related/equivalent HCC has been closed, but it is not appropriate for risk adjustment because it’s not a chronic diagnosis. - - name: recapture_flag - description: > + - 'ineligible for recapture': the specific HCC in question is “Open” and no related/equivalent HCC has been closed, but it is not appropriate for risk adjustment because it’s not a chronic diagnosis. + tests: + - accepted_values: + values: ['closed - higher coefficient hcc in hierarchy group', 'closed', 'closed - lower coefficient hcc in hierarchy group', 'new', 'open', 'ineligible for recapture'] + - name: recapturable_flag + description: > Whether or not the HCC is recapturable. Includes the following values: - - 'Y': condition is a chronic condition appropriate for recapture and has been documented in the prior 2 years - - 'N': condition is not a chronic condition appropriate for recapture or has not been documented in the prior 2 years + - '1': condition is a chronic condition appropriate for recapture and has been documented in the prior 2 years + - '0': condition is not a chronic condition appropriate for recapture or has not been documented in the prior 2 years + - name: hcc_type + description: > + The type of HCC. Includes the following values: + - 'coded': condition was coded in a claim during the collection year + - 'captured': condition is considered captured according to a different source besides claims, such as a clinical source + - 'suspect': condition is suspected to be present based on some other criteria besides claims diagnoses + tests: + - accepted_values: + values: ['coded', 'captured', 'suspect'] + - name: hcc_source + description: > + The source of the HCC information. Includes the following values: + - 'payer': HCC was identified in data received from the payer such as claims or suspect lists + - 'clinical': HCC was identified in clinical data such as the EHR + tests: + - accepted_values: + values: ['payer', 'clinical'] + - name: hcc_recapture__gap_status description: The gap status for each HCC. config: @@ -203,15 +233,17 @@ models: alias: gap_status tags: ["hcc_recapture", "final"] materialized: table - tests: + data_tests: - dbt_utils.unique_combination_of_columns: - combination_of_columns: - - person_id - - hcc_code - - payer - - model_version - - payment_year - - suspect_hcc_flag + arguments: + combination_of_columns: + - person_id + - hcc_code + - payer + - model_version + - payment_year + - hcc_type + - hcc_source columns: - name: person_id description: A unique identifier for a person. @@ -226,25 +258,25 @@ models: the 2025 CMS risk adjustment software. - name: model_version description: > - The CMS Medicare risk model version. This includes models such as v22,v24, and v28 which are documented in the - yearly rate announcement (e.g. https://www.cms.gov/files/document/2026-announcement.pdf) and on the CMS risk adjustment + The CMS Medicare risk model version. This includes models such as v22,v24, and v28 which are documented in the + yearly rate announcement (e.g. https://www.cms.gov/files/document/2026-announcement.pdf) and on the CMS risk adjustment website: https://www.cms.gov/medicare/payment/medicare-advantage-rates-statistics/risk-adjustment. - name: payment_year description: > - This is the year that the HCC should be coded. Typically the collection year + 1, but for open HCCs it will be the collection year + 2. - To illustrate, if we have an HCC claim in 2023, but not in 2024, that means it is open in 2024. The payment year is 2024 + 1 = 2025. So + This is the year that the HCC should be coded. Typically the collection year + 1, but for open HCCs it will be the collection year + 2. + To illustrate, if we have an HCC claim in 2023, but not in 2024, that means it is open in 2024. The payment year is 2024 + 1 = 2025. So an open HCC from 2023 will have a payment year = 2025. - - name: recapture_flag - description: > + - name: recapturable_flag + description: > Whether or not the HCC is recapturable. Includes the following values: - - 'Y': condition is a chronic condition appropriate for recapture and has been documented in the prior 2 years - - 'N': condition is not a chronic condition appropriate for recapture or has not been documented in the prior 2 years- name: recapture_flag + - '1': condition is a chronic condition appropriate for recapture and has been documented in the prior 2 years + - '0': condition is not a chronic condition appropriate for recapture or has not been documented in the prior 2 years - name: gap_status - desription: > + description: > definitions for gap_status: - 'closed using higher coefficient hcc in hierarchy group': An HCC in the same group was closed, but its coefficient is greater than the prior year HCC - 'closed': the specific HCC in question has been observed in a risk adjustable claim during the collection year. - 'closed using lower coefficient hcc in hierarchy group': An HCC in the same group was closed, but its coefficient is less than the prior year HCC - 'new': defined as an hcc that has not been coded in the past 2 years - 'open': for gaps and claims, it's a chronic condition appropriate for recapture that has not been documented in current collection year - - 'inappropriate for recapture': the specific HCC in question is “Open” and no related/equivalent HCC has been closed, but it is not appropriate for risk adjustment because it’s not a chronic diagnosis. + - 'ineligible for recapture': the specific HCC in question is “Open” and no related/equivalent HCC has been closed, but it is not appropriate for risk adjustment because it’s not a chronic diagnosis. diff --git a/models/data_marts/hcc_recapture/intermediate/hcc_recapture__int_all_hccs.sql b/models/data_marts/hcc_recapture/intermediate/hcc_recapture__int_all_hccs.sql deleted file mode 100644 index 5ad7970ff..000000000 --- a/models/data_marts/hcc_recapture/intermediate/hcc_recapture__int_all_hccs.sql +++ /dev/null @@ -1,166 +0,0 @@ -{{ config( - enabled = var('claims_enabled', False) | as_bool - ) -}} - -with seed_hcc_hierarchy as ( - select - model_version - , hcc_code - , hcc_hierarchy_group - , hcc_hierarchy_group_rank - from {{ ref('cms_hcc__disease_hierarchy_flat') }} -) - -, hcc_diagnosis as ( - select - payment_year - , diagnosis_code - , cms_hcc_v28 as hcc_code - , 'CMS-HCC-V28' as model_version - from {{ ref('cms_hcc__icd_10_cm_mappings') }} - where cms_hcc_v28_flag = 'Yes' - - union all - - select - payment_year - , diagnosis_code - , cms_hcc_v24 as hcc_code - , 'CMS-HCC-V24' as model_version - from {{ ref('cms_hcc__icd_10_cm_mappings') }} - where cms_hcc_v24_flag = 'Yes' -) - -, chronic_hccs as ( -select distinct - diag.hcc_code - , diag.payment_year - , diag.model_version - , 1 as chronic_flag -from {{ ref('chronic_conditions__cms_chronic_conditions_hierarchy') }} as hier -inner join hcc_diagnosis as diag - on hier.code = diag.diagnosis_code -) - -, get_risk_code as ( -select distinct - person_id - , payer - , payment_year - , model_version - , risk_model_code - , row_number() over (partition by person_id, payment_year, model_version order by collection_end_date desc) as month_order -from {{ ref('cms_hcc__int_demographic_factors') }} -where lower(factor_type) = 'demographic' -) - -, eligible_claims as ( --- Use distinct to remove claim line -select distinct - person_id - , claim_id - , payer -from {{ ref('cms_hcc__int_eligible_conditions') }} -) - -, medical_claims as ( --- Use distinct to remove claim line -select distinct - person_id - , payer - , claim_id - , rendering_npi -from {{ ref('core__medical_claim') }} -) - -, include_suspect_hccs as ( -select - person_id - , payer - , data_source - , recorded_date - , model_version - , claim_id - , hcc_code - , hcc_description - , condition_type - -- Listed as prior coding history since it comes from int_all_conditions, see hcc_suspecting__int_patient_hcc_history for reference - , 'Prior coding history' as reason - , 0 as suspect_hcc_flag --- Not using list_all since it doesn't have claim_id pulled through --- TODO: Update hcc_suspecting__list_all to have claim ID as well -from {{ ref('hcc_suspecting__int_all_conditions') }} -union all -select - person_id - , payer - , data_source - , suspect_date as recorded_date - , model_version - , null as claim_id - , hcc_code - , hcc_description - , 'suspect' as condition_type - , reason - , 1 as suspect_hcc_flag -from {{ ref('hcc_suspecting__list_all') }} --- Exclude since already included in int_all_conditions -where lower(reason) != 'prior coding history' -) - --- NOTE: Distinct is to remove different recording dates + ICD 10 codes for the same HCC code -select distinct - sus.person_id - , sus.payer - , sus.data_source - , {{ date_part('year', 'sus.recorded_date') }} as collection_year - , sus.recorded_date - , sus.model_version - , sus.claim_id - , sus.hcc_code - , sus.hcc_description - , chronic.chronic_flag as hcc_chronic_flag - , coalesce(hier.hcc_hierarchy_group, 'no hierarchy') as hcc_hierarchy_group - , coalesce(hier.hcc_hierarchy_group_rank, 1) as hcc_hierarchy_group_rank - , rcode.risk_model_code - , sus.condition_type - , case - when elig.claim_id is not null then 1 - when sus.suspect_hcc_flag = 1 then 1 - else 0 - end as eligible_claim_indicator - , case when elig_bene.person_id is not null then 1 else 0 end as eligible_bene - , med.rendering_npi - , reason - , suspect_hcc_flag -from include_suspect_hccs as sus -left outer join seed_hcc_hierarchy as hier - on sus.hcc_code = hier.hcc_code - and sus.model_version = hier.model_version -left outer join chronic_hccs as chronic - on sus.model_version = chronic.model_version - and sus.hcc_code = chronic.hcc_code - and {{ date_part('year', 'sus.recorded_date') }} = chronic.payment_year - 1 -left outer join get_risk_code as rcode - on sus.person_id = rcode.person_id - and sus.payer = rcode.payer - and {{ date_part('year', 'sus.recorded_date') }} = rcode.payment_year - 1 - and sus.model_version = rcode.model_version - and rcode.month_order = 1 -left outer join eligible_claims as elig - on sus.person_id = elig.person_id - and sus.payer = elig.payer - and sus.claim_id = elig.claim_id -left outer join medical_claims as med - on sus.person_id = med.person_id - and sus.payer = med.payer - and sus.claim_id = med.claim_id --- Only include benes eligible for gap closure -left outer join {{ ref('hcc_recapture__stg_eligible_benes') }} as elig_bene - on sus.person_id = elig_bene.person_id - and {{ date_part('year', 'sus.recorded_date') }} = elig_bene.collection_year - and sus.payer = elig_bene.payer -where sus.hcc_code is not null - -- Replace with cms_hcc__adjustment_rates once that table includes PY 2026 - and 1 = (case when {{ date_part('year', 'sus.recorded_date') }} >= 2025 and sus.model_version = 'CMS-HCC-V24' then 0 else 1 end) diff --git a/models/data_marts/hcc_recapture/intermediate/hcc_recapture__int_determine_gap_status.sql b/models/data_marts/hcc_recapture/intermediate/hcc_recapture__int_determine_gap_status.sql new file mode 100644 index 000000000..c34e699c3 --- /dev/null +++ b/models/data_marts/hcc_recapture/intermediate/hcc_recapture__int_determine_gap_status.sql @@ -0,0 +1,161 @@ +{{ config( + enabled = var('claims_enabled', False) | as_bool + ) +}} + +-- Get recapturable HCCs within the past 1 year +with filtered_hccs as ( + select * + from {{ ref('hcc_recapture__int_recapturable_hccs') }} + where filtered_by_hierarchy_flag = 0 +), + +risk_gaps as ( + select distinct + person_id + , payer + , collection_year + 1 as collection_year + , model_version + , hcc_code + , hcc_hierarchy_group + , hcc_hierarchy_group_rank + , suspect_hcc_flag + , recapturable_flag + , hcc_type + , hcc_source + , eligible_bene_flag + , risk_model_code + from filtered_hccs + where hcc_type = 'coded' + + union all + + -- No need to add +1 to collection year since these are already identified as captured/suspect in the same year identified + -- These come from other sources besides claims, such as payers + clinical data + select distinct + person_id + , payer + , collection_year + , model_version + , hcc_code + , hcc_hierarchy_group + , hcc_hierarchy_group_rank + , suspect_hcc_flag + , recapturable_flag + , hcc_type + , hcc_source + , eligible_bene_flag + , risk_model_code + from filtered_hccs + where hcc_type = 'suspect' +), + +best_past_rank as ( + select distinct + person_id + , payer + , collection_year + , model_version + , hcc_code + , hcc_hierarchy_group + , hcc_hierarchy_group_rank + , min(hcc_hierarchy_group_rank) over (partition by person_id, payer, collection_year, model_version, hcc_hierarchy_group) as best_past_rank + from filtered_hccs + where hcc_type in ('coded', 'captured') +), + + +best_current_rank as ( + select distinct + person_id + , payer + , collection_year + , model_version + , hcc_code + , hcc_hierarchy_group + , hcc_hierarchy_group_rank + , risk_model_code + , eligible_bene_flag + , min(hcc_hierarchy_group_rank) over (partition by person_id, payer, collection_year, model_version, hcc_hierarchy_group) as best_current_rank + from filtered_hccs +), + +equiv_coef as ( + select distinct + base.model_version + , base.hcc_hierarchy_group + , base.hcc_code + , base.risk_model_code + from {{ ref('hcc_recapture__stg_coef_hier') }} as base + inner join {{ ref('hcc_recapture__stg_coef_hier') }} as self + on base.hcc_hierarchy_group = self.hcc_hierarchy_group + and base.risk_model_code = self.risk_model_code + and base.coefficient = self.coefficient + and base.model_version = self.model_version + and base.hcc_code != self.hcc_code +) + +-- Note: Gaps can only be closed using claims received from the payor or from Athena. +select + coalesce(base.payer, gap.payer) as payer + , coalesce(base.person_id, gap.person_id) as person_id + , coalesce(base.risk_model_code, gap.risk_model_code, current_year_hier.risk_model_code) as risk_model_code + , coalesce(base.eligible_bene_flag, gap.eligible_bene_flag, current_year_hier.eligible_bene_flag) as eligible_bene_flag + , coalesce(base.hcc_code, gap.hcc_code) as hcc_code + , gap.hcc_code as recaptured_hcc_code + , current_year_hier.hcc_code as current_year_hcc_code + , grp.hcc_code as past_year_hcc_code + , coalesce(base.suspect_hcc_flag, gap.suspect_hcc_flag, 0) as suspect_hcc_flag + , coalesce(base.model_version, gap.model_version) as model_version + , coalesce(base.collection_year, gap.collection_year) as collection_year + , coalesce(base.hcc_hierarchy_group, gap.hcc_hierarchy_group) as hcc_hierarchy_group + , coalesce(base.hcc_hierarchy_group_rank, gap.hcc_hierarchy_group_rank) as hcc_hierarchy_group_rank + , coalesce(base.recapturable_flag, gap.recapturable_flag) as recapturable_flag + , coalesce(base.hcc_type, gap.hcc_type) as hcc_type + , coalesce(base.hcc_source, gap.hcc_source) as hcc_source + , case + when + gap.hcc_code is not null and base.hcc_code is not null and gap.hcc_code != base.hcc_code and equiv.risk_model_code is not null + then 'closed - equivalent coefficient hcc in hierarchy group' + when + grp.hcc_hierarchy_group is not null and base.hcc_hierarchy_group_rank < grp.best_past_rank + then 'closed - higher coefficient hcc in hierarchy group' + when current_year_hier.best_current_rank < gap.hcc_hierarchy_group_rank then 'closed - higher coefficient hcc in hierarchy group' + when gap.hcc_code is not null and base.hcc_code is not null then 'closed' + when + grp.hcc_hierarchy_group is not null and base.hcc_hierarchy_group_rank > grp.best_past_rank + then 'closed - lower coefficient hcc in hierarchy group' + when gap.hcc_code is not null and base.hcc_code is null then 'open' + when gap.hcc_code is null and base.hcc_code is not null then 'new' + end as gap_status +from filtered_hccs as base +full outer join risk_gaps as gap + on base.person_id = gap.person_id + and base.payer = gap.payer + and base.collection_year = gap.collection_year + and base.model_version = gap.model_version + and base.hcc_code = gap.hcc_code + -- Only coded or captured HCCs can close other HCCs + and base.hcc_type in ('coded', 'captured') +left join equiv_coef as equiv + on base.model_version = equiv.model_version + and base.hcc_hierarchy_group = equiv.hcc_hierarchy_group + and base.hcc_code = equiv.hcc_code + and base.risk_model_code = equiv.risk_model_code +left join best_past_rank as grp + on base.person_id = grp.person_id + and base.payer = grp.payer + and base.collection_year = grp.collection_year + and base.model_version = grp.model_version + and base.hcc_hierarchy_group = grp.hcc_hierarchy_group + and grp.best_past_rank = grp.hcc_hierarchy_group_rank +left join best_current_rank as current_year_hier + on gap.person_id = current_year_hier.person_id + and gap.payer = current_year_hier.payer + and gap.collection_year = current_year_hier.collection_year + and gap.model_version = current_year_hier.model_version + and gap.hcc_hierarchy_group = current_year_hier.hcc_hierarchy_group + and current_year_hier.best_current_rank = current_year_hier.hcc_hierarchy_group_rank +-- Gaps are only eligible to be closed by claims data and the base table here is closing the gap aliased table +-- The or hcc_type is null allows open HCCs to flow through +where base.hcc_type in ('coded', 'captured') or base.hcc_type is null diff --git a/models/data_marts/hcc_recapture/intermediate/hcc_recapture__int_eligible_benes.sql b/models/data_marts/hcc_recapture/intermediate/hcc_recapture__int_eligible_benes.sql new file mode 100644 index 000000000..904e18960 --- /dev/null +++ b/models/data_marts/hcc_recapture/intermediate/hcc_recapture__int_eligible_benes.sql @@ -0,0 +1,14 @@ +{{ config( + enabled = var('claims_enabled', False) | as_bool + ) +}} + +-- Flattening months to 1 person per year +select distinct + person_id + , collection_year + , payer +from {{ ref('hcc_recapture__stg_eligible_benes') }} +-- Null age groups leads to null risk model codes. +-- Risk model codes needs not to be null since it's used as a join argument to get equivalent coefficients. +where age_group is not null \ No newline at end of file diff --git a/models/data_marts/hcc_recapture/intermediate/hcc_recapture__int_gap_status.sql b/models/data_marts/hcc_recapture/intermediate/hcc_recapture__int_gap_status.sql index 14dc8ffc5..eeb1c47ec 100644 --- a/models/data_marts/hcc_recapture/intermediate/hcc_recapture__int_gap_status.sql +++ b/models/data_marts/hcc_recapture/intermediate/hcc_recapture__int_gap_status.sql @@ -3,231 +3,110 @@ ) }} -with eligible_hccs as ( - select - * - from {{ ref('hcc_recapture__int_hccs') }} -) - --- Get recapturable HCCs within the prior year -, recapturable_hccs as ( -select distinct - person_id - , payer - , collection_year + 1 as collection_year - , model_version - , hcc_code - , hcc_hierarchy_group - , hcc_hierarchy_group_rank - , suspect_hcc_flag -from eligible_hccs -) - -, best_past_rank as ( -select distinct - person_id - , payer - , collection_year - , model_version - , hcc_code - , hcc_hierarchy_group - , hcc_hierarchy_group_rank - , min(hcc_hierarchy_group_rank) over (partition by person_id, payer, collection_year, model_version, hcc_hierarchy_group) as best_past_rank -from recapturable_hccs --- This is only for what was actually coded in the past -where suspect_hcc_flag = 0 -) - - -, best_current_rank as ( -select distinct - person_id - , payer - , collection_year - , model_version - , hcc_code - , hcc_hierarchy_group - , hcc_hierarchy_group_rank - , min(hcc_hierarchy_group_rank) over (partition by person_id, payer, collection_year, model_version, hcc_hierarchy_group) as best_current_rank -from eligible_hccs -) - -, equiv_coef as ( -select distinct - base.model_version - , base.hcc_hierarchy_group - , base.hcc_code - , base.risk_model_code -from {{ ref('hcc_recapture__stg_coef_hier') }} as base -inner join {{ ref('hcc_recapture__stg_coef_hier') }} as self - on base.hcc_hierarchy_group = self.hcc_hierarchy_group - and base.risk_model_code = self.risk_model_code - and base.coefficient = self.coefficient - and base.model_version = self.model_version - and base.hcc_code != self.hcc_code -) - -, add_gap_status as ( -select - coalesce(recap.person_id, base.person_id) as person_id - , coalesce(recap.payer, base.payer) as payer - , coalesce(recap.hcc_code, base.hcc_code) as hcc_code - , coalesce(recap.suspect_hcc_flag, 0) as suspect_hcc_flag - , recap.hcc_code as recaptured_hcc_code - , current_year_hier.hcc_code as current_year_hcc_code - , grp.hcc_code as past_year_hcc_code - , coalesce(recap.model_version, base.model_version) as model_version - , coalesce(recap.collection_year, base.collection_year) as collection_year - , coalesce(recap.hcc_hierarchy_group, base.hcc_hierarchy_group) as hcc_hierarchy_group - , coalesce(recap.hcc_hierarchy_group_rank, base.hcc_hierarchy_group_rank) as hcc_hierarchy_group_rank - , base.risk_model_code - , case when recap.hcc_code is not null or grp.hcc_hierarchy_group is not null or base.hcc_chronic_flag = 1 then 1 else 0 end as recapture_flag - , eligible_bene - , case - when base.hcc_chronic_flag = 0 then 'inappropriate for recapture' - when recap.hcc_code is not null and base.hcc_code is not null and recap.hcc_code != base.hcc_code and equiv.risk_model_code is not null then 'closed - equivalent coefficient hcc in hierarchy group' - when grp.hcc_hierarchy_group is not null and base.hcc_hierarchy_group_rank < grp.best_past_rank then 'closed - higher coefficient hcc in hierarchy group' - when current_year_hier.best_current_rank < recap.hcc_hierarchy_group_rank then 'closed - higher coefficient hcc in hierarchy group' - when recap.hcc_code is not null and base.hcc_code is not null then 'closed' - when grp.hcc_hierarchy_group is not null and base.hcc_hierarchy_group_rank > grp.best_past_rank then 'closed - lower coefficient hcc in hierarchy group' - -- TODO: Should the current_year_hier be used to determine if something was closed by a lower coefficient hcc? - -- This was previously removed due to errors in primary key checks it was causing - when recap.hcc_code is not null and base.hcc_code is null then 'open' - when recap.hcc_code is null and base.hcc_code is not null and base.hcc_chronic_flag = 1 then 'new' - end as gap_status -from eligible_hccs as base -full outer join recapturable_hccs as recap - on base.person_id = recap.person_id - and base.payer = recap.payer - and base.collection_year = recap.collection_year - and base.model_version = recap.model_version - and base.hcc_code = recap.hcc_code -left outer join equiv_coef as equiv - on base.model_version = equiv.model_version - and base.hcc_hierarchy_group = equiv.hcc_hierarchy_group - and base.hcc_code = equiv.hcc_code - and base.risk_model_code = equiv.risk_model_code -left outer join best_past_rank as grp - on base.person_id = grp.person_id - and base.payer = grp.payer - and base.collection_year = grp.collection_year - and base.model_version = grp.model_version - and base.hcc_hierarchy_group = grp.hcc_hierarchy_group - and grp.best_past_rank = grp.hcc_hierarchy_group_rank -left outer join best_current_rank as current_year_hier - on recap.person_id = current_year_hier.person_id - and recap.payer = current_year_hier.payer - and recap.collection_year = current_year_hier.collection_year - and recap.model_version = current_year_hier.model_version - and recap.hcc_hierarchy_group = current_year_hier.hcc_hierarchy_group - and current_year_hier.best_current_rank = current_year_hier.hcc_hierarchy_group_rank --- Filtering to just discharge diagnosis since gaps are only eligible to be closed by claims data and the base table here is closing the recap aliased table --- The or condition_type is null allows open HCCs to flow through -where lower(base.condition_type) = 'discharge_diagnosis' or base.condition_type is null -) - -- Need to do this for HCCs in more than 1 group such as HCC 409 in v28 -, rank_gap_status as ( -select - person_id - , payer - , hcc_code - , suspect_hcc_flag - , model_version - , collection_year + 1 as payment_year - , recapture_flag - , eligible_bene - , gap_status - , risk_model_code - , hcc_hierarchy_group - , hcc_hierarchy_group_rank - -- just used for deduping - , case gap_status - when 'inappropriate for recapture' then 1 - when 'closed - equivalent coefficient hcc in hierarchy group' then 2 - when 'closed - higher coefficient hcc in hierarchy group' then 3 - when 'closed' then 4 - when 'closed - lower coefficient hcc in hierarchy group' then 5 - when 'open' then 6 - when 'new' then 7 - end as gap_status_rank -from add_gap_status +with add_rankings as ( + select + person_id + , payer + , hcc_code + , suspect_hcc_flag + , model_version + , collection_year + 1 as payment_year + , recapturable_flag + , hcc_type + , hcc_source + , eligible_bene_flag + , gap_status + , risk_model_code + , hcc_hierarchy_group + , hcc_hierarchy_group_rank + , case when hcc_type = 'coded' then 1 when hcc_type = 'captured' then 2 else 3 end as hcc_type_rank + -- just used for deduping + , case gap_status + when 'closed - equivalent coefficient hcc in hierarchy group' then 1 + when 'closed - higher coefficient hcc in hierarchy group' then 2 + when 'closed' then 3 + when 'closed - lower coefficient hcc in hierarchy group' then 4 + when 'new' then 5 -- New comes before open since suspects will always be open, but coded will sometimes say it is new + when 'open' then 6 + end as gap_status_rank + from {{ ref('hcc_recapture__int_determine_gap_status') }} ) -, min_gap_status as ( -select - person_id - , payer - , hcc_code - , suspect_hcc_flag - , model_version - , payment_year - , risk_model_code - , recapture_flag - , eligible_bene - , gap_status - , gap_status_rank - , hcc_hierarchy_group - , hcc_hierarchy_group_rank - , min(gap_status_rank) over (partition by person_id, payer, hcc_code, model_version, payment_year) as min_gap_status_rank -from rank_gap_status +-- Pick the best hcc type + -- 1. Best rank + -- 2. If tie → prefer recapture (suspect_hcc_flag = 0) +, best_hcc_type as ( + select * + , row_number() over ( + partition by person_id, payer, hcc_code, model_version, payment_year + order by + hcc_type_rank asc, + suspect_hcc_flag asc -- 0 preferred over 1 + ) as best_rank + from add_rankings ) -- Pick the best gap status , best_gap_status as ( -select - * -from min_gap_status -where min_gap_status_rank = gap_status_rank + select + * + from ( + select + * + , min(gap_status_rank) over (partition by person_id, payer, hcc_code, model_version, payment_year) as min_gap_status_rank + from best_hcc_type + where best_rank = 1 + ) + where min_gap_status_rank = gap_status_rank ) -- Find the minimum hierarchy for open hccs , min_open_hierarchy as ( -select - person_id - , payer - , payment_year - , model_version - , hcc_hierarchy_group - , suspect_hcc_flag - , min(hcc_hierarchy_group_rank) as min_hcc_hier_group_rank -from best_gap_status -group by - person_id - , payer - , payment_year - , model_version - , hcc_hierarchy_group - , suspect_hcc_flag + select + person_id + , payer + , payment_year + , model_version + , hcc_hierarchy_group + , min(hcc_hierarchy_group_rank) as min_hcc_hier_group_rank + from best_gap_status + group by + person_id + , payer + , payment_year + , model_version + , hcc_hierarchy_group ) -- Using distinct to deduplicate select distinct - bgap.person_id + bgap.person_id , bgap.payer , bgap.hcc_code , bgap.risk_model_code , bgap.model_version , bgap.payment_year - , bgap.recapture_flag + , bgap.recapturable_flag + , bgap.hcc_type + , bgap.hcc_source , bgap.gap_status , bgap.hcc_hierarchy_group , bgap.hcc_hierarchy_group_rank , bgap.suspect_hcc_flag -- Apply hierarchies (i.e. if the hierarchy is not the min hierarchy, then remove it) - , case when bgap.hcc_hierarchy_group is not null and mhier.hcc_hierarchy_group is null then 1 else 0 end as filtered_out_by_hierarchy + , case when bgap.hcc_hierarchy_group is not null and mhier.hcc_hierarchy_group is null then 1 else 0 end as filtered_by_hierarchy_flag from best_gap_status as bgap -left outer join min_open_hierarchy as mhier - on bgap.person_id = mhier.person_id - and bgap.payer = mhier.payer - and bgap.payment_year = mhier.payment_year - and bgap.model_version = mhier.model_version - and bgap.hcc_hierarchy_group = mhier.hcc_hierarchy_group - and bgap.hcc_hierarchy_group_rank = mhier.min_hcc_hier_group_rank - and bgap.suspect_hcc_flag = mhier.suspect_hcc_flag --- Join eligible benes again here to capture new rows with open gaps -inner join {{ ref('hcc_recapture__stg_eligible_benes') }} as elig - on bgap.person_id = elig.person_id - and bgap.payment_year = elig.collection_year + 1 - and bgap.payer = elig.payer +left join min_open_hierarchy as mhier + on bgap.person_id = mhier.person_id + and bgap.payer = mhier.payer + and bgap.payment_year = mhier.payment_year + and bgap.model_version = mhier.model_version + and bgap.hcc_hierarchy_group = mhier.hcc_hierarchy_group + and bgap.hcc_hierarchy_group_rank = mhier.min_hcc_hier_group_rank + -- Join eligible benes again here to capture new rows with open gaps +inner join {{ ref('hcc_recapture__int_eligible_benes') }} as elig + on bgap.person_id = elig.person_id + and bgap.payment_year = elig.collection_year + 1 + and bgap.payer = elig.payer where 1 = (case when bgap.payment_year >= 2026 and bgap.model_version = 'CMS-HCC-V24' then 0 else 1 end) diff --git a/models/data_marts/hcc_recapture/intermediate/hccs/hcc_recapture__int_all_hccs.sql b/models/data_marts/hcc_recapture/intermediate/hccs/hcc_recapture__int_all_hccs.sql new file mode 100644 index 000000000..e3c03e414 --- /dev/null +++ b/models/data_marts/hcc_recapture/intermediate/hccs/hcc_recapture__int_all_hccs.sql @@ -0,0 +1,90 @@ +{{ config( + enabled = var('claims_enabled', False) | as_bool + ) +}} +with seed_hcc_hierarchy as ( + select + model_version + , hcc_code + , hcc_hierarchy_group + , hcc_hierarchy_group_rank + from {{ ref('cms_hcc__disease_hierarchy_flat') }} +) + +, get_risk_code as ( + select distinct + person_id + , payer + , payment_year + , model_version + , risk_model_code + , row_number() over (partition by person_id, payment_year, model_version order by collection_end_date desc) as month_order + from {{ ref('cms_hcc__int_demographic_factors') }} + where lower(factor_type) = 'demographic' +) + +, medical_claims as ( +-- Use distinct to remove claim line + select distinct + person_id + , payer + , claim_id + , rendering_npi + from {{ ref('core__medical_claim') }} +) + +, eligible_hccs as ( + select * from {{ ref('hcc_recapture__int_coded_hccs') }} + + union all + + select * from {{ ref('hcc_recapture__int_suspect_hccs') }} +) + +-- NOTE: Distinct is to remove different recording dates + ICD 10 codes for the same HCC code +select distinct + sus.person_id + , sus.payer + , sus.data_source + , {{ date_part('year', 'sus.recorded_date') }} as collection_year + , sus.recorded_date + , sus.model_version + , sus.claim_id + , sus.hcc_code + , sus.hcc_description + , chronic.chronic_flag as hcc_chronic_flag + , coalesce(hier.hcc_hierarchy_group, 'no hierarchy') as hcc_hierarchy_group + , coalesce(hier.hcc_hierarchy_group_rank, 1) as hcc_hierarchy_group_rank + , rcode.risk_model_code + , case when elig_bene.person_id is not null then 1 else 0 end as eligible_bene_flag + , eligible_claim_flag + , med.rendering_npi + , suspect_hcc_flag + , case when chronic.chronic_flag = 1 and eligible_claim_flag = 1 then 1 else 0 end as recapturable_flag + , hcc_type + , hcc_source +from eligible_hccs as sus +left join seed_hcc_hierarchy as hier + on sus.hcc_code = hier.hcc_code + and sus.model_version = hier.model_version +left join {{ ref('hcc_recapture__stg_chronic_hccs') }} as chronic + on sus.model_version = chronic.model_version + and sus.hcc_code = chronic.hcc_code +left join get_risk_code as rcode + on sus.person_id = rcode.person_id + and sus.payer = rcode.payer + and {{ date_part('year', 'sus.recorded_date') }} = rcode.payment_year - 1 + and sus.model_version = rcode.model_version + and rcode.month_order = 1 +left join medical_claims as med + on sus.person_id = med.person_id + and sus.payer = med.payer + and sus.claim_id = med.claim_id +-- Only include benes eligible for gap closure +left join {{ ref('hcc_recapture__int_eligible_benes') }} as elig_bene + on sus.person_id = elig_bene.person_id + and {{ date_part('year', 'sus.recorded_date') }} = elig_bene.collection_year + and sus.payer = elig_bene.payer +where sus.hcc_code is not null + -- Replace with cms_hcc__adjustment_rates once that table includes PY 2026 + and 1 = (case when {{ date_part('year', 'sus.recorded_date') }} >= 2025 and sus.model_version = 'CMS-HCC-V24' then 0 else 1 end) \ No newline at end of file diff --git a/models/data_marts/hcc_recapture/intermediate/hccs/hcc_recapture__int_coded_hccs.sql b/models/data_marts/hcc_recapture/intermediate/hccs/hcc_recapture__int_coded_hccs.sql new file mode 100644 index 000000000..e2775fce8 --- /dev/null +++ b/models/data_marts/hcc_recapture/intermediate/hccs/hcc_recapture__int_coded_hccs.sql @@ -0,0 +1,38 @@ +{{ config( + enabled = var('claims_enabled', False) | as_bool + ) +}} + +with eligible_claims as ( +-- Use distinct to remove claim line + select distinct + person_id + , claim_id + , payer + from {{ ref('cms_hcc__int_eligible_conditions') }} +) + +select + cond.person_id + , cond.payer + , cond.data_source + , cond.recorded_date + , cond.model_version + , cond.claim_id + , cond.hcc_code + , cond.hcc_description + , 0 as suspect_hcc_flag + , case + when elig.claim_id is not null then 1 + else 0 + end as eligible_claim_flag + , 'coded' as hcc_type + , 'payer' as hcc_source +-- Not using list_all since it doesn't have claim_id pulled through +-- TODO: Update hcc_suspecting__list_all to have claim ID as well +from {{ ref('hcc_suspecting__int_all_conditions') }} as cond +left join eligible_claims as elig + on cond.person_id = elig.person_id + and cond.payer = elig.payer + and cond.claim_id = elig.claim_id +where lower(condition_type) = 'discharge_diagnosis' \ No newline at end of file diff --git a/models/data_marts/hcc_recapture/intermediate/hcc_recapture__int_hccs.sql b/models/data_marts/hcc_recapture/intermediate/hccs/hcc_recapture__int_recapturable_hccs.sql similarity index 75% rename from models/data_marts/hcc_recapture/intermediate/hcc_recapture__int_hccs.sql rename to models/data_marts/hcc_recapture/intermediate/hccs/hcc_recapture__int_recapturable_hccs.sql index 3ecfc6eb1..f81f9480e 100644 --- a/models/data_marts/hcc_recapture/intermediate/hcc_recapture__int_hccs.sql +++ b/models/data_marts/hcc_recapture/intermediate/hccs/hcc_recapture__int_recapturable_hccs.sql @@ -6,10 +6,8 @@ with base as ( select * -from {{ ref('hcc_recapture__int_all_hccs') }} --- hierarchies should only be applied to eligible claims -where eligible_claim_indicator = 1 - and hcc_chronic_flag = 1 +from {{ ref('hcc_recapture__int_all_hccs')}} +where recapturable_flag = 1 ) , min_hierarchy as ( @@ -21,6 +19,7 @@ select , data_source , hcc_hierarchy_group , suspect_hcc_flag + , hcc_chronic_flag , min(hcc_hierarchy_group_rank) as min_hcc_hier_group_rank from base group by @@ -31,6 +30,7 @@ group by , data_source , hcc_hierarchy_group , suspect_hcc_flag + , hcc_chronic_flag ) select @@ -47,14 +47,15 @@ select , base.hcc_hierarchy_group , base.hcc_hierarchy_group_rank , base.risk_model_code - , base.eligible_bene - , base.eligible_claim_indicator + , base.eligible_bene_flag , base.rendering_npi - , base.reason - , base.condition_type , base.suspect_hcc_flag + , base.recapturable_flag + , base.hcc_type + , base.hcc_source + , CASE WHEN mhier.hcc_hierarchy_group IS NOT NULL THEN 0 ELSE 1 END as filtered_by_hierarchy_flag from base -left outer join min_hierarchy as mhier +left join min_hierarchy as mhier on base.person_id = mhier.person_id and base.payer = mhier.payer and base.collection_year = mhier.collection_year @@ -63,5 +64,4 @@ left outer join min_hierarchy as mhier and base.hcc_hierarchy_group = mhier.hcc_hierarchy_group and base.hcc_hierarchy_group_rank = mhier.min_hcc_hier_group_rank and base.suspect_hcc_flag = mhier.suspect_hcc_flag --- Apply hierarchies -where mhier.hcc_hierarchy_group is not null + and base.hcc_chronic_flag = mhier.hcc_chronic_flag diff --git a/models/data_marts/hcc_recapture/intermediate/hccs/hcc_recapture__int_suspect_hccs.sql b/models/data_marts/hcc_recapture/intermediate/hccs/hcc_recapture__int_suspect_hccs.sql new file mode 100644 index 000000000..7a680213f --- /dev/null +++ b/models/data_marts/hcc_recapture/intermediate/hccs/hcc_recapture__int_suspect_hccs.sql @@ -0,0 +1,28 @@ +{{ config( + enabled = var('claims_enabled', False) | as_bool + ) +}} + +select + person_id + , payer + , data_source + , suspect_date as recorded_date + , model_version + , null as claim_id + , hcc_code + , hcc_description + , 1 as suspect_hcc_flag + , 1 as eligible_claim_flag + , 'suspect' as hcc_type + , 'payer' as hcc_source +from {{ ref('hcc_suspecting__list_all') }} +-- Exclude since already included in int_all_conditions +where lower(reason) != 'prior coding history' + +{% if var('hcc_recapture_suspect_list') %} +union all +select + * +from {{ ref('hcc_recapture__stg_suspect_hccs')}} +{% endif %} \ No newline at end of file diff --git a/models/data_marts/hcc_recapture/intermediate_models.yml b/models/data_marts/hcc_recapture/intermediate_models.yml index fe1dde6dd..b8ec0acfe 100644 --- a/models/data_marts/hcc_recapture/intermediate_models.yml +++ b/models/data_marts/hcc_recapture/intermediate_models.yml @@ -10,21 +10,21 @@ models: alias: int_all_hccs tags: ["hcc_recapture", "intermediate"] materialized: table - tests: + data_tests: - dbt_utils.unique_combination_of_columns: - combination_of_columns: - - person_id - - hcc_code - - data_source - - payer - - model_version - - collection_year - - hcc_hierarchy_group - - recorded_date - - rendering_npi - - claim_id - - condition_type - - reason + arguments: + combination_of_columns: + - person_id + - hcc_code + - data_source + - payer + - model_version + - collection_year + - hcc_hierarchy_group + - recorded_date + - rendering_npi + - claim_id + - suspect_hcc_flag columns: - name: person_id description: A unique identifier for a person. @@ -36,8 +36,8 @@ models: description: The date the claim was originally recorded. Based on admission date, but if null then filled in by claim start date followed by claim end date. - name: model_version description: > - The CMS Medicare risk model version. This includes models such as v22,v24, and v28 which are documented in the - yearly rate announcement (e.g. https://www.cms.gov/files/document/2026-announcement.pdf) and on the CMS risk adjustment + The CMS Medicare risk model version. This includes models such as v22,v24, and v28 which are documented in the + yearly rate announcement (e.g. https://www.cms.gov/files/document/2026-announcement.pdf) and on the CMS risk adjustment website: https://www.cms.gov/medicare/payment/medicare-advantage-rates-statistics/risk-adjustment - name: hcc_code description: The hierarchical condition category code. @@ -55,49 +55,164 @@ models: For example, in 2025, the hierarchies were stored in a file called `V28115H1.txt`. The file will end in an H. - name: hcc_hierarchy_group_rank description: > - The rank within the HCC hierarchy group. The lower the number, the higher the rank. When 2 HCCs within the same group are + The rank within the HCC hierarchy group. The lower the number, the higher the rank. When 2 HCCs within the same group are coded within the same collection year, the HCC with the lower rank will be chosen of the two for a given beneficiary. - name: risk_model_code description: > There are different coefficients depending on a few factors such as dual eligibility, new enrollee, institutional status, etc... This column provides acronyms based off of the CMS risk adjustment SAS code. For example, these can be found in the `C2824T2N_25.csv` file for the 2025 CMS risk adjustment software. - - name: eligible_bene + - name: eligible_bene_flag description: A 1 is indicated for a beneficiary who is in the eligibility files. A 0 is indicated if they are not in the eligibility files. - name: rendering_npi description: The National Provider Identifier (NPI) of the physician who rendered services to the beneficiary. - - name: reason - description: Free text reason for the appointment or service. - - name: condition_type - description: The type of condition. Claims are filled in with values such as `discharge_diagnosis`. - - name: eligible_claim_indicator + - name: eligible_claim_flag description: > Whether the claim is eligible for risk adjustment as determined based on a list of accepted CPT/HCPCs codes from CMS. These are available on the CMS risk adjustment website: https://www.cms.gov/medicare/payment/medicare-advantage-rates-statistics/risk-adjustment - - name: hcc_recapture__int_hccs + + - name: hcc_recapture__int_gap_status + description: > + Determines the gap for a given HCC code. A gap is based on prior year claims which qualified as an HCC. If there was a chronic HCC in a prior year, + it would be expected this same HCC was coded in a future year. + config: + schema: | + {%- if var('tuva_schema_prefix',None) != None -%}{{var('tuva_schema_prefix')}}_hcc_recapture + {% else %}hcc_recapture{%- endif -%} + alias: int_gap_status + tags: ["hcc_recapture", "intermediate"] + materialized: table + data_tests: + - dbt_utils.unique_combination_of_columns: + arguments: + combination_of_columns: + - person_id + - hcc_code + - payer + - model_version + - payment_year + - hcc_hierarchy_group + - hcc_type + - hcc_source + columns: + - name: person_id + description: A unique identifier for a person. + - name: payer + description: The name of the person (i.e. beneficiary) insurance provider. + - name: hcc_code + description: The hierarchical condition category code. + - name: risk_model_code + description: > + There are different coefficients depending on a few factors such as dual eligibility, new enrollee, institutional status, etc... + This column provides acronyms based off of the CMS risk adjustment SAS code. For example, these can be found in the `C2824T2N_25.csv` file for + the 2025 CMS risk adjustment software. + - name: model_version + description: > + The CMS Medicare risk model version. This includes models such as v22,v24, and v28 which are documented in the + yearly rate announcement (e.g. https://www.cms.gov/files/document/2026-announcement.pdf) and on the CMS risk adjustment + website: https://www.cms.gov/medicare/payment/medicare-advantage-rates-statistics/risk-adjustment + - name: payment_year + description: > + This is the year that the HCC should be coded. Typically the collection year + 1, but for open HCCs it will be the collection year + 2. + To illustrate, if we have an HCC claim in 2023, but not in 2024, that means it is open in 2024. The payment year is 2024 + 1 = 2025. So + an open HCC from 2023 will have a payment year = 2025. + - name: recapturable_flag + description: > + Whether or not the HCC is recapturable. Includes the following values: + - '1': condition is a chronic condition appropriate for recapture and has been documented in the prior 2 years + - '0': condition is not a chronic condition appropriate for recapture or has not been documented in the prior 2 years + - name: gap_status + description: > + definitions for gap_status: + - 'closed using higher coefficient hcc in hierarchy group': An HCC in the same group was closed, but its coefficient is greater than the prior year HCC + - 'closed': the specific HCC in question has been observed in a risk adjustable claim during the collection year. + - 'closed using lower coefficient hcc in hierarchy group': An HCC in the same group was closed, but its coefficient is less than the prior year HCC + - 'new': defined as an hcc that has not been coded in the past 2 years + - 'open': for gaps and claims, it's a chronic condition appropriate for recapture that has not been documented in current collection year + - 'ineligible for recapture': the specific HCC in question is “Open” and no related/equivalent HCC has been closed, but it is not appropriate for risk adjustment because it’s not a chronic diagnosis. + - name: hcc_hierarchy_group + description: > + The name of the HCC hierarchy group. Determined based off of the CMS risk adjustment website model software. + For example, in 2025, the hierarchies were stored in a file called `V28115H1.txt`. The file will end in an H. + - name: hcc_hierarchy_group_rank + description: > + The rank within the HCC hierarchy group. The lower the number, the higher the rank. When 2 HCCs within the same group are + coded within the same collection year, the HCC with the lower rank will be chosen of the two for a given beneficiary. + - name: filtered_by_hierarchy_flag + description: > + This is a 1 if this HCC gets filtered out due to a hierarchy being applied or not. The hierarchy is re-applied in this model due to interactions between + open HCCs and closed HCCs originating in different collection years. + + - name: hcc_recapture__int_eligible_benes + description: > + Beneficiaries eligible for HCC recapture. When ESRD risk scores are available in the Tuva project, + the filter removing ESRD beneficiaries will need to be removed. + config: + schema: | + {%- if var('tuva_schema_prefix',None) != None -%}{{var('tuva_schema_prefix')}}_hcc_recapture + {% else %}hcc_recapture{%- endif -%} + alias: int_eligible_benes + tags: ["hcc_recapture", "intermediate"] + materialized: table + data_tests: + - dbt_utils.unique_combination_of_columns: + arguments: + combination_of_columns: + - person_id + - collection_year + - payer + columns: + - name: person_id + description: A unique identifier for a person. + - name: collection_year + description: The year that the claims originated and were originally coded. + - name: payer + description: The name of the person (i.e. beneficiary) insurance provider. + + - name: hcc_recapture__int_determine_gap_status + description: Labelling HCCs with a gap status + config: + schema: | + {%- if var('tuva_schema_prefix',None) != None -%}{{var('tuva_schema_prefix')}}_hcc_recapture + {% else %}hcc_recapture{%- endif -%} + alias: int_determine_gap_status + tags: ["hcc_recapture", "intermediate"] + materialized: table + + - name: hcc_recapture__int_coded_hccs + description: HCCs that were found based off of claims information. + config: + schema: | + {%- if var('tuva_schema_prefix',None) != None -%}{{var('tuva_schema_prefix')}}_hcc_recapture + {% else %}hcc_recapture{%- endif -%} + alias: int_coded_hccs + tags: ["hcc_recapture", "intermediate"] + materialized: table + + - name: hcc_recapture__int_recapturable_hccs description: Filter to just eligible claims and apply HCC hierarchies. config: schema: | {%- if var('tuva_schema_prefix',None) != None -%}{{var('tuva_schema_prefix')}}_hcc_recapture {% else %}hcc_recapture{%- endif -%} - alias: int_hccs + alias: int_recapturable_hccs tags: ["hcc_recapture", "intermediate"] materialized: table - tests: + data_tests: - dbt_utils.unique_combination_of_columns: - combination_of_columns: - - person_id - - hcc_code - - data_source - - payer - - model_version - - collection_year - - hcc_hierarchy_group - - recorded_date - - rendering_npi - - claim_id - - condition_type - - suspect_hcc_flag + arguments: + combination_of_columns: + - person_id + - hcc_code + - data_source + - payer + - model_version + - collection_year + - hcc_hierarchy_group + - recorded_date + - rendering_npi + - claim_id + - suspect_hcc_flag columns: - name: person_id description: A unique identifier for a person. @@ -111,7 +226,7 @@ models: description: > The CMS Medicare risk model version. This includes models such as v22,v24, and v28 which are documented in the yearly rate announcement (e.g. https://www.cms.gov/files/document/2026-announcement.pdf) and on the CMS risk adjustment - website: https://www.cms.gov/medicare/payment/medicare-advantage-rates-statistics/risk-adjustment + website: https://www.cms.gov/medicare/payment/medicare-advantage-rates-statistics/risk-adjustment - name: hcc_code description: The hierarchical condition category code. - name: hcc_description @@ -134,85 +249,25 @@ models: description: > There are different coefficients depending on a few factors such as dual eligibility, new enrollee, institutional status, etc... This column provides acronyms based off of the CMS risk adjustment SAS code. For example, these can be found in the `C2824T2N_25.csv` file for - the 2025 CMS risk adjustment software. - - name: eligible_bene + the 2025 CMS risk adjustment software. + - name: eligible_bene_flag description: A 1 is indicated for a beneficiary who is in the eligibility files. A 0 is indicated if they are not in the eligibility files. - name: rendering_npi description: The National Provider Identifier (NPI) of the physician who rendered services to the beneficiary. - - name: reason - description: Free text reason for the appointment or service. - - name: condition_type - description: The type of condition. Claims are filled in with values such as `discharge_diagnosis`. - - name: eligible_claim_indicator + - name: eligible_claim_flag description: > Whether the claim is eligible for risk adjustment as determined based on a list of accepted CPT/HCPCs codes from CMS. These are available - on the CMS risk adjustment website: https://www.cms.gov/medicare/payment/medicare-advantage-rates-statistics/risk-adjustment - - name: hcc_recapture__int_gap_status - description: > - Determines the gap for a given HCC code. A gap is based on prior year claims which qualified as an HCC. If there was a chronic HCC in a prior year, - it would be expected this same HCC was coded in a future year. + on the CMS risk adjustment website: https://www.cms.gov/medicare/payment/medicare-advantage-rates-statistics/risk-adjustment + + - name: hcc_recapture__int_suspect_hccs + description: Suspect HCCs which are identified from sources besides medical claims conditions. config: schema: | {%- if var('tuva_schema_prefix',None) != None -%}{{var('tuva_schema_prefix')}}_hcc_recapture {% else %}hcc_recapture{%- endif -%} - alias: int_gap_status + alias: int_suspect_hccs tags: ["hcc_recapture", "intermediate"] materialized: table - tests: - - dbt_utils.unique_combination_of_columns: - combination_of_columns: - - person_id - - hcc_code - - payer - - model_version - - payment_year - - hcc_hierarchy_group - - suspect_hcc_flag - columns: - - name: person_id - description: A unique identifier for a person. - - name: payer - description: The name of the person (i.e. beneficiary) insurance provider. - - name: hcc_code - description: The hierarchical condition category code. - - name: risk_model_code - description: > - There are different coefficients depending on a few factors such as dual eligibility, new enrollee, institutional status, etc... - This column provides acronyms based off of the CMS risk adjustment SAS code. For example, these can be found in the `C2824T2N_25.csv` file for - the 2025 CMS risk adjustment software. - - name: model_version - description: > - The CMS Medicare risk model version. This includes models such as v22,v24, and v28 which are documented in the - yearly rate announcement (e.g. https://www.cms.gov/files/document/2026-announcement.pdf) and on the CMS risk adjustment - website: https://www.cms.gov/medicare/payment/medicare-advantage-rates-statistics/risk-adjustment - - name: payment_year - description: > - This is the year that the HCC should be coded. Typically the collection year + 1, but for open HCCs it will be the collection year + 2. - To illustrate, if we have an HCC claim in 2023, but not in 2024, that means it is open in 2024. The payment year is 2024 + 1 = 2025. So - an open HCC from 2023 will have a payment year = 2025. - - name: recapture_flag - description: > - Whether or not the HCC is recapturable. Includes the following values: - - 'Y': condition is a chronic condition appropriate for recapture and has been documented in the prior 2 years - - 'N': condition is not a chronic condition appropriate for recapture or has not been documented in the prior 2 years - - name: gap_status - description: > - definitions for gap_status: - - 'closed using higher coefficient hcc in hierarchy group': An HCC in the same group was closed, but its coefficient is greater than the prior year HCC - - 'closed': the specific HCC in question has been observed in a risk adjustable claim during the collection year. - - 'closed using lower coefficient hcc in hierarchy group': An HCC in the same group was closed, but its coefficient is less than the prior year HCC - - 'new': defined as an hcc that has not been coded in the past 2 years - - 'open': for gaps and claims, it's a chronic condition appropriate for recapture that has not been documented in current collection year - - 'inappropriate for recapture': the specific HCC in question is “Open” and no related/equivalent HCC has been closed, but it is not appropriate for risk adjustment because it’s not a chronic diagnosis. - - name: hcc_hierarchy_group - description: > - The name of the HCC hierarchy group. Determined based off of the CMS risk adjustment website model software. - For example, in 2025, the hierarchies were stored in a file called `V28115H1.txt`. The file will end in an H. - - name: hcc_hierarchy_group_rank - description: > - The rank within the HCC hierarchy group. The lower the number, the higher the rank. When 2 HCCs within the same group are - coded within the same collection year, the HCC with the lower rank will be chosen of the two for a given beneficiary. - - name: filtered_out_by_hierarchy - description: > - This is a 1 if this HCC gets filtered out due to a hierarchy being applied or not. The hierarchy is re-applied in this model due to interactions between - open HCCs and closed HCCs originating in different collection years. + + + \ No newline at end of file diff --git a/models/data_marts/hcc_recapture/staging/hcc_recapture__stg_chronic_hccs.sql b/models/data_marts/hcc_recapture/staging/hcc_recapture__stg_chronic_hccs.sql new file mode 100644 index 000000000..1bd6b3486 --- /dev/null +++ b/models/data_marts/hcc_recapture/staging/hcc_recapture__stg_chronic_hccs.sql @@ -0,0 +1,49 @@ + +{% if var('hcc_recapture_chronic_hccs') %} + +select + cast(hcc_code as {{dbt.type_string()}}) as hcc_code + , cast(model_version as {{dbt.type_string()}}) as model_version + , cast(chronic_flag as {{dbt.type_int()}}) as chronic_flag +from {{ ref('chronic_hccs') }} + +{% else %} + +with hcc_diagnosis as ( + select + payment_year + , diagnosis_code + , cms_hcc_v28 as hcc_code + , 'CMS-HCC-V28' as model_version + from {{ ref('cms_hcc__icd_10_cm_mappings') }} + where cms_hcc_v28_flag = 'Yes' + + union all + + select + payment_year + , diagnosis_code + , cms_hcc_v24 as hcc_code + , 'CMS-HCC-V24' as model_version + from {{ ref('cms_hcc__icd_10_cm_mappings') }} + where cms_hcc_v24_flag = 'Yes' +) + +, chronic_hccs as ( +select distinct + diag.hcc_code + , diag.model_version + , 1 as chronic_flag +from {{ ref('chronic_conditions__cms_chronic_conditions_hierarchy') }} as hier +inner join hcc_diagnosis as diag + on hier.code = diag.diagnosis_code +where diag.payment_year = (select max(payment_year) from hcc_diagnosis) +) + +select + hcc_code + , model_version + , chronic_flag +from chronic_hccs + +{% endif %} \ No newline at end of file diff --git a/models/data_marts/hcc_recapture/staging/hcc_recapture__stg_eligible_benes.sql b/models/data_marts/hcc_recapture/staging/hcc_recapture__stg_eligible_benes.sql index 3cb8f099a..20d4cab96 100644 --- a/models/data_marts/hcc_recapture/staging/hcc_recapture__stg_eligible_benes.sql +++ b/models/data_marts/hcc_recapture/staging/hcc_recapture__stg_eligible_benes.sql @@ -7,6 +7,7 @@ select distinct person_id , {{ date_part('year', 'collection_end_date') }} as collection_year + , age_group , payer from {{ ref('cms_hcc__int_members') }} -- Don't support ESRD risk scores yet diff --git a/models/data_marts/hcc_recapture/staging/hcc_recapture__stg_suspect_hccs.sql b/models/data_marts/hcc_recapture/staging/hcc_recapture__stg_suspect_hccs.sql new file mode 100644 index 000000000..0e9756a91 --- /dev/null +++ b/models/data_marts/hcc_recapture/staging/hcc_recapture__stg_suspect_hccs.sql @@ -0,0 +1,19 @@ +{{ config( + enabled = var('hcc_recapture_suspect_list') | as_bool + ) +}} + +select + cast(person_id as {{dbt.type_string()}}) as person_id + , cast(payer as {{dbt.type_string()}}) as payer + , cast(data_source as {{dbt.type_string()}}) as data_source + , cast(recorded_date as date) as recorded_date + , cast(model_version as {{dbt.type_string()}}) as model_version + , cast(claim_id as {{dbt.type_string()}}) as claim_id + , cast(hcc_code as {{dbt.type_string()}}) as hcc_code + , cast(hcc_description as {{dbt.type_string()}}) as hcc_description + , cast(suspect_hcc_flag as {{dbt.type_int()}}) as suspect_hcc_flag + , cast(eligible_claim_flag as {{dbt.type_int()}}) as eligible_claim_flag + , cast(hcc_type as {{dbt.type_string()}}) as hcc_type + , cast(hcc_source as {{dbt.type_string()}}) as hcc_source +from {{ ref('suspect_hccs')}} diff --git a/models/data_marts/hcc_recapture/staging_models.yml b/models/data_marts/hcc_recapture/staging_models.yml index 91664e7dd..3d8a4f18d 100644 --- a/models/data_marts/hcc_recapture/staging_models.yml +++ b/models/data_marts/hcc_recapture/staging_models.yml @@ -12,9 +12,10 @@ models: alias: stg_coef_hier tags: ["hcc_recapture", "staging"] materialized: table - tests: + data_tests: - dbt_utils.unique_combination_of_columns: - combination_of_columns: + arguments: + combination_of_columns: - model_version - hcc_code - hcc_hierarchy_group @@ -27,7 +28,7 @@ models: yearly rate announcement (e.g. https://www.cms.gov/files/document/2026-announcement.pdf) and on the CMS risk adjustment website: https://www.cms.gov/medicare/payment/medicare-advantage-rates-statistics/risk-adjustment - name: hcc_code - description: The hierarchical condition category code. + description: The hierarchical condition category code. - name: hcc_hierarchy_group description: > The name of the HCC hierarchy group. Determined based off of the CMS risk adjustment website model software. @@ -41,6 +42,7 @@ models: There are different coefficients depending on a few factors such as dual eligibility, new enrollee, institutional status, etc... This column provides acronyms based off of the CMS risk adjustment SAS code. For example, these can be found in the `C2824T2N_25.csv` file for the 2025 CMS risk adjustment software. + - name: hcc_recapture__stg_eligible_benes description: > Beneficiaries eligible for HCC recapture. When ESRD risk scores are available in the Tuva project, @@ -52,12 +54,14 @@ models: alias: stg_eligible_benes tags: ["hcc_recapture", "staging"] materialized: table - tests: + data_tests: - dbt_utils.unique_combination_of_columns: - combination_of_columns: - - person_id - - collection_year - - payer + arguments: + combination_of_columns: + - person_id + - collection_year + - payer + - age_group columns: - name: person_id description: A unique identifier for a person. @@ -65,3 +69,35 @@ models: description: The year that the claims originated and were originally coded. - name: payer description: The name of the person (i.e. beneficiary) insurance provider. + - name: age_group + tests: + - not_null: + description: > + If there is no age, it is likely because they are missing a birth date in the input layer. + Risk model codes needs not to be null since it's used as a join argument to get equivalent coefficients. + Members without age groups are excluded. + config: + severity: warn + + - name: hcc_recapture__stg_suspect_hccs + description: | + A list of suspected HCCs. This is an optional model which can be included by + setting the hcc_recapture_suspect_list variable in your dbt_project.yml + config: + schema: | + {%- if var('tuva_schema_prefix',None) != None -%}{{var('tuva_schema_prefix')}}_hcc_recapture + {% else %}hcc_recapture{%- endif -%} + alias: stg_suspect_hccs + tags: ["hcc_recapture", "staging"] + + - name: hcc_recapture__stg_chronic_hccs + description: | + A list of suspected HCCs. This is an optional model which can be included by + setting the hcc_recapture_suspect_list variable in your dbt_project.yml + config: + schema: | + {%- if var('tuva_schema_prefix',None) != None -%}{{var('tuva_schema_prefix')}}_hcc_recapture + {% else %}hcc_recapture{%- endif -%} + alias: stg_chronic_hccs + tags: ["hcc_recapture", "staging"] + \ No newline at end of file diff --git a/seeds/value_sets/cms_hcc/cms_hcc_seeds.yml b/seeds/value_sets/cms_hcc/cms_hcc_seeds.yml index 1477a41df..5d0eae2e6 100644 --- a/seeds/value_sets/cms_hcc/cms_hcc_seeds.yml +++ b/seeds/value_sets/cms_hcc/cms_hcc_seeds.yml @@ -161,7 +161,7 @@ seeds: schema: | {%- if var('tuva_schema_prefix',None) != None -%}{{var('tuva_schema_prefix')}}_value_sets{% else %}value_sets{%- endif -%} alias: _value_set_disease_factors - tags: cms_hcc + tags: ["cms_hcc", "hcc_recapture"] enabled: "{{ var('claims_enabled', False) }}" column_types: model_version: | @@ -243,7 +243,7 @@ seeds: schema: | {%- if var('tuva_schema_prefix',None) != None -%}{{var('tuva_schema_prefix')}}_value_sets{% else %}value_sets{%- endif -%} alias: _value_set_disease_hierarchy_flat - tags: cms_hcc + tags: ["cms_hcc", "hcc_recapture"] enabled: "{{ var('claims_enabled', False) }}" column_types: hcc_code: | @@ -386,7 +386,7 @@ seeds: schema: | {%- if var('tuva_schema_prefix',None) != None -%}{{var('tuva_schema_prefix')}}_value_sets{% else %}value_sets{%- endif -%} alias: _value_set_cms_hcc_icd_10_cm_mappings - tags: cms_hcc + tags: ["cms_hcc", "hcc_recapture"] enabled: "{{ var('claims_enabled', False) }}" column_types: payment_year: | diff --git a/tests/check_claim_type_not_only_undetermined.sql b/tests/check_claim_type_not_only_undetermined.sql deleted file mode 100644 index 89aeec314..000000000 --- a/tests/check_claim_type_not_only_undetermined.sql +++ /dev/null @@ -1,23 +0,0 @@ -{{ config( - enabled = var('claims_enabled', False) - | as_bool, - tags = ['dqi', 'tuva_dqi_sev_2', 'dqi_service_categories', 'dqi_ccsr', 'dqi_cms_chronic_conditions', - 'dqi_tuva_chronic_conditions', 'dqi_cms_hccs', 'dqi_ed_classification', - 'dqi_financial_pmpm', 'dqi_quality_measures', 'dqi_readmission'], - severity = 'warn' - ) -}} - -with claim_type_cte as ( - select - data_source - , min(case when claim_type = 'undetermined' then 1 else 0 end) as has_only_undetermined_claim_types - , count(*) as count_records - from {{ ref('input_layer__medical_claim') }} - group by data_source -) - -select - * -from claim_type_cte -where has_only_undetermined_claim_types = 1 diff --git a/tests/check_medical_claim_eligibility_overlap.sql b/tests/check_medical_claim_eligibility_overlap.sql deleted file mode 100644 index c3aa4f081..000000000 --- a/tests/check_medical_claim_eligibility_overlap.sql +++ /dev/null @@ -1,70 +0,0 @@ -{{ config( - enabled = var('claims_enabled', False) - | as_bool, - tags = ['dqi', 'tuva_dqi_sev_2', 'dqi_service_categories', 'dqi_ccsr', 'dqi_cms_chronic_conditions', - 'dqi_tuva_chronic_conditions', 'dqi_cms_hccs', 'dqi_ed_classification', - 'dqi_financial_pmpm', 'dqi_quality_measures', 'dqi_readmission'], - severity = 'warn' - ) -}} - -with eligibility as ( - select distinct - person_id - , data_source - from {{ ref('input_layer__eligibility') }} -) - -, medical_claims as ( - select distinct - person_id - , data_source - from {{ ref('input_layer__medical_claim') }} -) - -, mc_records_check as ( - select - data_source - , count(*) as n_rows - from medical_claims - group by data_source -) - -, elig_records_check as ( - select - data_source - , count(*) as n_rows - from eligibility - group by data_source -) - -, overlap_check as ( - select - m.data_source - , count(e.person_id) as n_rows - from medical_claims as m - left outer join eligibility as e - on m.person_id = e.person_id - and m.data_source = e.data_source - group by m.data_source -) - -, final as ( - select - oc.data_source - , oc.n_rows as n_overlapping_records - , case when (mc.n_rows = 0 or mc.n_rows is null) then 1 else 0 end as is_mc_empty - , case when (ec.n_rows = 0 or ec.n_rows is null) then 1 else 0 end as is_elig_empty - from overlap_check as oc - left outer join mc_records_check as mc - on oc.data_source = mc.data_source - left outer join elig_records_check as ec - on oc.data_source = ec.data_source -) - -select - data_source - , n_overlapping_records -from final -where not (is_mc_empty = 1 or is_elig_empty = 1) -and n_overlapping_records = 0 diff --git a/tests/check_pharmacy_claim_eligibility_overlap.sql b/tests/check_pharmacy_claim_eligibility_overlap.sql deleted file mode 100644 index 1653a4339..000000000 --- a/tests/check_pharmacy_claim_eligibility_overlap.sql +++ /dev/null @@ -1,70 +0,0 @@ -{{ config( - enabled = var('claims_enabled', False) - | as_bool, - tags = ['dqi', 'tuva_dqi_sev_2', 'dqi_service_categories', 'dqi_ccsr', 'dqi_cms_chronic_conditions', - 'dqi_tuva_chronic_conditions', 'dqi_cms_hccs', 'dqi_ed_classification', - 'dqi_financial_pmpm', 'dqi_quality_measures', 'dqi_readmission'], - severity = 'warn' - ) -}} - -with eligibility as ( - select distinct - person_id - , data_source - from {{ ref('input_layer__eligibility') }} -) - -, pharmacy_claims as ( - select distinct - person_id - , data_source - from {{ ref('input_layer__pharmacy_claim') }} -) - -, pc_records_check as ( - select - data_source - , count(*) as n_rows - from pharmacy_claims - group by data_source -) - -, elig_records_check as ( - select - data_source - , count(*) as n_rows - from eligibility - group by data_source -) - -, overlap_check as ( - select - p.data_source - , count(e.person_id) as n_rows - from pharmacy_claims as p - left outer join eligibility as e - on p.person_id = e.person_id - and p.data_source = e.data_source - group by p.data_source -) - -, final as ( - select - oc.data_source - , oc.n_rows as n_overlapping_records - , case when (pc.n_rows = 0 or pc.n_rows is null) then 1 else 0 end as is_pc_empty - , case when (ec.n_rows = 0 or ec.n_rows is null) then 1 else 0 end as is_elig_empty - from overlap_check as oc - left outer join pc_records_check as pc - on oc.data_source = pc.data_source - left outer join elig_records_check as ec - on oc.data_source = ec.data_source -) - -select - data_source - , n_overlapping_records -from final -where not (is_pc_empty = 1 or is_elig_empty = 1) -and n_overlapping_records = 0 diff --git a/tests/check_pmpm_paid_amount_threshold.sql b/tests/check_pmpm_paid_amount_threshold.sql deleted file mode 100644 index 55bae3edf..000000000 --- a/tests/check_pmpm_paid_amount_threshold.sql +++ /dev/null @@ -1,23 +0,0 @@ -{{ config( - enabled = var('claims_enabled', False) - | as_bool, - tags = ['dqi', 'tuva_dqi_sev_5', 'dqi_service_categories', 'dqi_ccsr', 'dqi_cms_chronic_conditions', - 'dqi_tuva_chronic_conditions', 'dqi_cms_hccs', 'dqi_ed_classification', - 'dqi_financial_pmpm', 'dqi_quality_measures', 'dqi_readmission'], - severity = 'warn' - ) -}} - -select - payer - , {{ quote_column('plan') }} - , data_source - , count(year_month) as member_months - , avg(total_paid) as avg_paid_pmpm -from {{ ref('financial_pmpm__pmpm_prep') }} -group by - payer - , data_source - , {{ quote_column('plan') }} -having avg(total_paid) > 10000 -and count(year_month) >= 100 diff --git a/tests/test_complete_hccs.sql b/tests/test_complete_hccs.sql new file mode 100644 index 000000000..2d69b9577 --- /dev/null +++ b/tests/test_complete_hccs.sql @@ -0,0 +1,13 @@ +--- This test ensures all HCCs are still accounted for after determining the gap status +select distinct person_id, payer, collection_year, model_version, hcc_code +from {{ ref('hcc_recapture__int_recapturable_hccs')}} +where eligible_bene_flag = 1 + and hcc_type = 'coded' + and filtered_by_hierarchy_flag = 0 + +{{ dbt.except() }} + +select distinct person_id, payer, payment_year - 1, model_version, hcc_code +-- Using the int gap status since hierarchies aren't applied yet +from {{ ref('hcc_recapture__int_gap_status')}} +where gap_status != 'open' \ No newline at end of file diff --git a/tests/test_hierarchies.sql b/tests/test_hierarchies.sql new file mode 100644 index 000000000..68b4ee8a2 --- /dev/null +++ b/tests/test_hierarchies.sql @@ -0,0 +1,23 @@ +with base as ( +select distinct + person_id, + payer, + payment_year, + model_version, + hcc_hierarchy_group, + hcc_hierarchy_group_rank +from {{ ref('hcc_recapture__int_gap_status') }} +where hcc_hierarchy_group != 'no hierarchy' + and filtered_by_hierarchy_flag = 0 +) + +select + person_id, + payer, + payment_year, + model_version, + hcc_hierarchy_group, + count(*) +from base +group by all +having count(*) > 1 diff --git a/tests/test_null_gap_status.sql b/tests/test_null_gap_status.sql new file mode 100644 index 000000000..39c58e2ba --- /dev/null +++ b/tests/test_null_gap_status.sql @@ -0,0 +1,30 @@ +/* This test checks to ensure that all null gap status are due to one of the following reasons: +- Not a chronic HCC +- Is a suspect HCC (only when it overlaps with an actual claim) +- Was not an eligible claim +- Was filtered out due to a hierarchy + +If this test fails, that means that the logic needs to be reviewed to find what other reasons +are causing ineligible recaptures and, if needed, adjust this test accordingly. +*/ +select stat.* +from {{ ref('hcc_recapture__hcc_status') }} as stat +left join {{ ref('hcc_recapture__int_gap_status') }} gaps + on stat.person_id = gaps.person_id + and stat.hcc_code = gaps.hcc_code + and stat.payer = gaps.payer + and stat.model_version = gaps.model_version + and stat.payment_year = gaps.payment_year + and stat.suspect_hcc_flag = gaps.suspect_hcc_flag +left join {{ ref('hcc_recapture__int_recapturable_hccs')}} ext + on stat.person_id = ext.person_id + and stat.payer = ext.payer + and stat.data_source = ext.data_source + and stat.payment_year = ext.collection_year + 1 + and stat.hcc_code = ext.hcc_code + and stat.suspect_hcc_flag = ext.suspect_hcc_flag +where stat.gap_status = 'ineligible for recapture' + and stat.hcc_chronic_flag = 1 + and stat.suspect_hcc_flag = 0 + and stat.eligible_claim_flag = 1 + and stat.filtered_by_hierarchy_flag = 0 \ No newline at end of file diff --git a/tests/test_recapture_rates.sql b/tests/test_recapture_rates.sql new file mode 100644 index 000000000..a9653e993 --- /dev/null +++ b/tests/test_recapture_rates.sql @@ -0,0 +1,12 @@ +-- Ensure the latest YTD value matches the recapture rates +select + ytd.payer + , ytd.payment_year +from {{ref('hcc_recapture__recapture_rates_monthly_ytd')}} ytd +left join {{ref('hcc_recapture__recapture_rates')}} recap + on ytd.payer = recap.payer + and ytd.payment_year = recap.payment_year + and ytd.ytd_recapture_rate = recap.recapture_rate +where + month(ytd.payment_year_month) = 12 + and recap.payer is null