Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e4bdddb
BFD-4764: Prior Auth Server Implementation
mel1-G Jul 7, 2026
e1604e1
Suppress warnings for duplicate string literals for displays
mel1-G Jul 7, 2026
a082274
refactor to query prior auth in parallel with claims + some clean up
mel1-G Jul 8, 2026
41c98c5
resolve merge conflicts
mel1-G Jul 8, 2026
cebd6e8
filter SAMHSA prior auths & rework source filter
mel1-G Jul 10, 2026
2f91dda
support prior auth search through _tag & determine system for HIPPS cd
mel1-G Jul 20, 2026
ee5babd
rework SystemType so each type outlines its compatible sources
mel1-G Jul 20, 2026
419d74d
update mapping for HCPCS_OR_CPT_OR_HIPPS and remove data absent check…
mel1-G Jul 21, 2026
bb250bb
remove old todo comment
mel1-G Jul 21, 2026
09667ad
resolve merge conflict & bump migration
mel1-G Jul 21, 2026
5f64971
clean up filters for determining whether or not PA should be queried
mel1-G Jul 21, 2026
b1721d5
fix port mappings to not conflict with other db
aschey-forpeople Jul 22, 2026
440d86d
refactor samhsa tests for prior authorization
mel1-G Jul 23, 2026
a107871
expanded samhsa pa coverage and did some clean up
mel1-G Jul 24, 2026
9a53005
guard against false positives with checking if code is SAMHSA
mel1-G Jul 27, 2026
cbd96e2
resolve merge conflict
mel1-G Jul 27, 2026
6fa02d3
revert getOrDefault for systems check
mel1-G Jul 27, 2026
97d152c
address mapping mismatches
mel1-G Jul 28, 2026
386ef78
resolve merge conflict
mel1-G Jul 28, 2026
dfbbe5a
bump migration
mel1-G Jul 28, 2026
3fc6c39
Merge branch 'master' into BFD-4764
alex-dzeda Jul 29, 2026
579857c
bump migration again :(
mel1-G Jul 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
ALTER TABLE idr.prior_auth
ADD COLUMN bfd_order_refer_careteam_name character varying(135),
ADD COLUMN bfd_order_refer_npi_type integer,
ADD COLUMN bfd_render_careteam_name character varying(135),
ADD COLUMN bfd_render_npi_type integer,
ADD COLUMN bfd_att_phy_careteam_name character varying(135),
ADD COLUMN bfd_att_phy_npi_type integer;

ALTER TABLE idr.prior_auth
ADD COLUMN resource_id UUID DEFAULT gen_random_uuid();

CREATE TABLE idr.prior_auth_item (
mbi_num VARCHAR(11) NOT NULL,
utn VARCHAR(14) NOT NULL,
current_segment INT NOT NULL,
hcpcs_or_cpt_or_hipps VARCHAR(5) NOT NULL,
price_mod1 VARCHAR(2) NOT NULL,
price_mod2 VARCHAR(2) NOT NULL,
place_of_serv VARCHAR(2) NOT NULL,
rev_code_1 VARCHAR(4) NOT NULL,
pa_dt_added DATE NOT NULL,
pa_dt_updated DATE,
pa_decision VARCHAR(1) NOT NULL,
pa_req_sub_dt DATE NOT NULL,
pa_req_rec_dt DATE NOT NULL,
pa_decision_dt DATE,
pa_decision_exp_dt DATE,
service_cnts INT NOT NULL,
svc_render_st VARCHAR(2) NOT NULL,
mr_count_ind INT NOT NULL,
mr_count_st_dt DATE,
mr_count_end_dt DATE,
rrb_excl_ind VARCHAR(1) NOT NULL,
bfd_created_ts TIMESTAMPTZ NOT NULL,
bfd_updated_ts TIMESTAMPTZ,
PRIMARY KEY(mbi_num, utn, current_segment)
);

ALTER TABLE idr.prior_auth
DROP CONSTRAINT prior_auth_pkey,
DROP COLUMN current_segment,
ADD CONSTRAINT prior_auth_pkey PRIMARY KEY (mbi_num, utn);

ALTER TABLE idr.prior_auth
DROP COLUMN hcpcs_or_cpt_or_hipps,
DROP COLUMN price_mod1,
DROP COLUMN price_mod2,
DROP COLUMN place_of_serv,
DROP COLUMN rev_code_1,
DROP COLUMN pa_dt_added,
DROP COLUMN pa_dt_updated,
DROP COLUMN pa_decision,
DROP COLUMN pa_req_sub_dt,
DROP COLUMN pa_req_rec_dt,
DROP COLUMN pa_decision_dt,
DROP COLUMN pa_decision_exp_dt,
DROP COLUMN service_cnts,
DROP COLUMN svc_render_st,
DROP COLUMN mr_count_ind,
DROP COLUMN mr_count_st_dt,
DROP COLUMN mr_count_end_dt,
DROP COLUMN rrb_excl_ind;

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
ALTER TABLE idr.prior_auth
ADD COLUMN bfd_order_refer_careteam_name character varying(135),
ADD COLUMN bfd_order_refer_npi_type integer,
ADD COLUMN bfd_render_careteam_name character varying(135),
ADD COLUMN bfd_render_npi_type integer,
ADD COLUMN bfd_att_phy_careteam_name character varying(135),
ADD COLUMN bfd_att_phy_npi_type integer;
52 changes: 0 additions & 52 deletions apps/bfd-model-idr/maps/ExplanationOfBenefit-Helper.map
Original file line number Diff line number Diff line change
Expand Up @@ -358,58 +358,6 @@ conceptmap "CLM_PD_STUS_CD" {
s:"T" == t:"partial"
}

conceptmap "CLM_AUDT_TRL_STUS_CD_MCS" {
prefix s = "https://hl7.org/fhir/string"
prefix t = "https://hl7.org/fhir/string"

s:"A" = t:"queued"
s:"B" = t:"queued"
s:"C" = t:"partial"
s:"D" = t:"complete"
s:"E" = t:"complete"
s:"F" = t:"complete"
s:"G" = t:"complete"
s:"J" = t:"queued"
s:"K" = t:"queued"
s:"L" = t:"partial"
s:"M" = t:"complete"
s:"N" = t:"complete"
s:"P" = t:"complete"
s:"Q" = t:"complete"
s:"R" = t:"complete"
s:"U" = t:"complete"
s:"V" = t:"complete"
s:"W" = t:"complete"
s:"X" = t:"complete"
s:"Y" = t:"complete"
s:"Z" = t:"complete"
s:"1" = t:"queued"
s:"2" = t:"queued"
s:"3" = t:"partial"
s:"4" = t:"complete"
s:"5" = t:"complete"
s:"6" = t:"error"
s:"8" = t:"complete"
s:"9" = t:"complete"
}

// Separate concept map for FISS since outcome differs for codes F & M
conceptmap "CLM_AUDT_TRL_STUS_CD_FISS" {
prefix s = "https://hl7.org/fhir/string"
prefix t = "https://hl7.org/fhir/string"

s:"A" == t:"queued"
s:"F" == t:"partial"
s:"I" == t:"partial"
s:"S" == t:"partial"
s:"M" == t:"partial"
s:"P" == t:"complete"
s:"R" == t:"complete"
s:"D" == t:"complete"
s:"T" == t:"partial"
s:"U" == t:"complete"
}

conceptmap "CLM_DGNS_PRCDR_ICD_IND_PROCEDURE" {
prefix s = "https://hl7.org/fhir/string"
prefix t = "https://hl7.org/fhir/string"
Expand Down
4 changes: 2 additions & 2 deletions apps/bfd-model-idr/maps/ExplanationOfBenefit-PriorAuth.map
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ group createEOBPriorAuth(source src: ExplanationOfBenefitPA, target tgt: BFDEOB)
} "Create metadata";

src -> tgt.type = create('CodeableConcept') as tgtCC then {
//CodeSystem -> CLM_TYPE_CD
//CodeSystem -> CLM_TYPE
//Two types - 1: PDEX, 2: CMS-specific. use concept map
src.CLM_TYPE as typeCd -> tgtCC.coding = translate(typeCd,'https://bfd.cms.gov/MappingLanguage/Maps/ExplanationOfBenefit-Helper#PriorAuthType','coding') "translate code";
//No blanks here since 2023.
Expand Down Expand Up @@ -236,7 +236,7 @@ group addSvcRenderSt(source src: String, target tgt: Extension){

group addMRCountInd(source src: String, target tgt: Extension){
src -> tgt.url = "https://bluebutton.cms.gov/fhir/StructureDefinition/MR-COUNT-IND" "Add extension url";
src as val -> tgt.value = cast(val, "decimal") "set value";
src as val -> tgt.value = cast(val, 'unsignedInt') "set value";
}

group addMRCountStDt(source src: String, target tgt: Extension){
Expand Down
2 changes: 2 additions & 0 deletions apps/bfd-pipeline-idr/load_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
from model.idr_contract_pbp_contact import IdrContractPbpContact
from model.idr_contract_pbp_number import IdrContractPbpNumber
from model.idr_prior_auth import IdrPriorAuth
from model.idr_prior_auth_item import IdrPriorAuthItem
from pydantic_utils import fields

LOAD_EVENTS_TABLE = "source_load_events"
Expand Down Expand Up @@ -88,6 +89,7 @@
IdrBeneficiaryMaPartDEnrollment,
IdrBeneficiaryMaPartDEnrollmentRx,
IdrPriorAuth,
IdrPriorAuthItem,
}
}
_HPMS_TABLES = {
Expand Down
14 changes: 14 additions & 0 deletions apps/bfd-pipeline-idr/model/base_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,16 @@ def provider_careteam_name_expr(alias: str, type: str | None) -> str:
"""


def provider_npi_type_expr(alias: str) -> str:
return f"""
CASE
WHEN {alias}.prvdr_lgl_name IS NOT NULL THEN 2
Comment thread
mel1-G marked this conversation as resolved.
WHEN {alias}.prvdr_lgl_name IS NULL AND {alias}.prvdr_1st_name IS NOT NULL THEN 1
ELSE NULL
END
"""


MEDICARE_EXHAUSTED_CD = "A3"
ACTIVE_CARE_CD = "22"
QUALIFYING_STAY_CD = "70"
Expand Down Expand Up @@ -372,6 +382,10 @@ def base_claim_filter(partition: LoadPartition) -> str:
ALIAS_PRVDR_ATNDG = "prvdr_atndg"
ALIAS_PRVDR_OPRTG = "prvdr_oprtg"
ALIAS_PRVDR_OTHR = "prvdr_othr"
ALIAS_PRVDR_ATT_PHY = "prvdr_att"
ALIAS_PRVDR_RENDER = "prvdr_render"
ALIAS_PRVDR_ORDER_REFER = "prvdr_order_refer"
ALIAS_PRIOR_AUTH = "prior_auth"
ALIAS_XREF = "xref"
ALIAS_LCTN_HSTRY = "lctn_hstry"
ALIAS_CLM_GRP = "clm_grp"
Expand Down
65 changes: 43 additions & 22 deletions apps/bfd-pipeline-idr/model/idr_prior_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,23 @@

from pydantic import BeforeValidator

from constants import IDR_PRIOR_AUTH_TABLE
from constants import DEFAULT_MAX_DATE, IDR_PRIOR_AUTH_TABLE, IDR_PROVIDER_HISTORY_TABLE
from load_partition import LoadPartition
from model.base_model import (
ALIAS_PRIOR_AUTH,
ALIAS_PRVDR_ATT_PHY,
ALIAS_PRVDR_ORDER_REFER,
ALIAS_PRVDR_RENDER,
BATCH_TIMESTAMP,
EXPR,
INSERT_EXCLUDE,
PRIMARY_KEY_ORDER,
UPDATE_TIMESTAMP,
IdrBaseModel,
ModelType,
Source,
provider_careteam_name_expr,
provider_npi_type_expr,
transform_default_string,
transform_null_date_to_max,
transform_null_date_to_min,
Expand All @@ -23,37 +30,38 @@
class IdrPriorAuth(IdrBaseModel):
mbi_num: Annotated[str, {PRIMARY_KEY_ORDER: 0}]
utn: Annotated[str, {PRIMARY_KEY_ORDER: 1}]
current_segment: Annotated[int, {PRIMARY_KEY_ORDER: 2}]
utn_valid_st_dt: Annotated[date, BeforeValidator(transform_null_date_to_min)]
utn_valid_en_dt: Annotated[date, BeforeValidator(transform_null_date_to_max)]
clm_type: Annotated[str, BeforeValidator(transform_default_string)]
hcpcs_or_cpt_or_hipps: str
mac_id: str
pa_dt_added: date
pa_dt_updated: Annotated[date, BeforeValidator(transform_null_date_to_max)]
service_cnts: int
pa_decision: str
pa_req_sub_dt: date
pa_req_rec_dt: date
pa_decision_dt: Annotated[date, BeforeValidator(transform_null_date_to_min)]
pa_decision_exp_dt: Annotated[date, BeforeValidator(transform_null_date_to_max)]
order_refer_npi: Annotated[str, BeforeValidator(transform_default_string)]
bfd_order_refer_careteam_name: Annotated[
str,
{EXPR: provider_careteam_name_expr(ALIAS_PRVDR_ORDER_REFER, None)},
BeforeValidator(transform_default_string),
]
bfd_order_refer_npi_type: Annotated[
int | None, {EXPR: provider_npi_type_expr(ALIAS_PRVDR_ORDER_REFER)}
]
render_npi: Annotated[str, BeforeValidator(transform_default_string)]
svc_render_st: str
place_of_serv: Annotated[str, BeforeValidator(transform_default_string)]
price_mod1: Annotated[str, BeforeValidator(transform_default_string)]
price_mod2: Annotated[str, BeforeValidator(transform_default_string)]
bfd_render_careteam_name: Annotated[
str,
{EXPR: provider_careteam_name_expr(ALIAS_PRVDR_RENDER, None)},
BeforeValidator(transform_default_string),
]
bfd_render_npi_type: Annotated[int | None, {EXPR: provider_npi_type_expr(ALIAS_PRVDR_RENDER)}]
icn_dcn: Annotated[str, BeforeValidator(transform_default_string)]
cms_cert: Annotated[str, BeforeValidator(transform_default_string)]
npi: Annotated[str, BeforeValidator(transform_default_string)]
name: str
rev_code_1: Annotated[str, BeforeValidator(transform_default_string)]
tob: Annotated[str, BeforeValidator(transform_default_string)]
mr_count_ind: int
mr_count_st_dt: Annotated[date, BeforeValidator(transform_null_date_to_min)]
mr_count_end_dt: Annotated[date, BeforeValidator(transform_null_date_to_max)]
att_phy_npi: Annotated[str, BeforeValidator(transform_default_string)]
rrb_excl_ind: Annotated[str, BeforeValidator(transform_default_string)]
bfd_att_phy_careteam_name: Annotated[
str,
{EXPR: provider_careteam_name_expr(ALIAS_PRVDR_ATT_PHY, None)},
BeforeValidator(transform_default_string),
]
bfd_att_phy_npi_type: Annotated[int | None, {EXPR: provider_npi_type_expr(ALIAS_PRVDR_ATT_PHY)}]
# TBD: might have to change the insert & update ts once IDR adds those
idr_insrt_ts: Annotated[datetime, {BATCH_TIMESTAMP: True, INSERT_EXCLUDE: True}]
idr_updt_ts: Annotated[
Expand Down Expand Up @@ -81,13 +89,26 @@ def model_type() -> ModelType:
@classmethod
def fetch_query(cls, partition: LoadPartition, start_time: datetime, source: Source) -> str:
# Prior auth data older than the lookback period should be filtered
prior_auth = ALIAS_PRIOR_AUTH
prvdr_att_phy = ALIAS_PRVDR_ATT_PHY
prvdr_order_refer = ALIAS_PRVDR_ORDER_REFER
prvdr_render = ALIAS_PRVDR_RENDER
return f"""
WITH distinct_prior_auths AS (
SELECT *, ROW_NUMBER()
OVER (PARTITION BY mbi_num, utn, current_segment ORDER BY mbi_num) as row_order
OVER (PARTITION BY mbi_num, utn ORDER BY current_segment) as row_order
FROM {IDR_PRIOR_AUTH_TABLE}
WHERE pa_req_rec_dt > '{MIN_PRIOR_AUTH_LOAD_DATE}'
)
SELECT {{COLUMNS}} FROM distinct_prior_auths
SELECT {{COLUMNS}} FROM distinct_prior_auths {prior_auth}
LEFT JOIN {IDR_PROVIDER_HISTORY_TABLE} {prvdr_att_phy}
ON {prvdr_att_phy}.prvdr_npi_num = {prior_auth}.att_phy_npi
AND {prvdr_att_phy}.prvdr_hstry_obslt_dt >= '{DEFAULT_MAX_DATE}'
LEFT JOIN {IDR_PROVIDER_HISTORY_TABLE} {prvdr_order_refer}
ON {prvdr_order_refer}.prvdr_npi_num = {prior_auth}.order_refer_npi
AND {prvdr_order_refer}.prvdr_hstry_obslt_dt >= '{DEFAULT_MAX_DATE}'
LEFT JOIN {IDR_PROVIDER_HISTORY_TABLE} {prvdr_render}
ON {prvdr_render}.prvdr_npi_num = {prior_auth}.render_npi
AND {prvdr_render}.prvdr_hstry_obslt_dt >= '{DEFAULT_MAX_DATE}'
{{WHERE_CLAUSE}} AND row_order = 1;
"""
83 changes: 83 additions & 0 deletions apps/bfd-pipeline-idr/model/idr_prior_auth_item.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
from datetime import date, datetime
from typing import Annotated, override

from pydantic import BeforeValidator

from constants import IDR_PRIOR_AUTH_TABLE
from load_partition import LoadPartition
from model.base_model import (
ALIAS_PRIOR_AUTH,
BATCH_TIMESTAMP,
INSERT_EXCLUDE,
PRIMARY_KEY_ORDER,
UPDATE_TIMESTAMP,
IdrBaseModel,
ModelType,
Source,
transform_default_string,
transform_null_date_to_max,
transform_null_date_to_min,
)
from settings import MIN_PRIOR_AUTH_LOAD_DATE


class IdrPriorAuthItem(IdrBaseModel):
mbi_num: Annotated[str, {PRIMARY_KEY_ORDER: 0}]
utn: Annotated[str, {PRIMARY_KEY_ORDER: 1}]
current_segment: Annotated[int, {PRIMARY_KEY_ORDER: 2}]
hcpcs_or_cpt_or_hipps: str
price_mod1: Annotated[str, BeforeValidator(transform_default_string)]
price_mod2: Annotated[str, BeforeValidator(transform_default_string)]
place_of_serv: Annotated[str, BeforeValidator(transform_default_string)]
rev_code_1: Annotated[str, BeforeValidator(transform_default_string)]
pa_dt_added: date
pa_dt_updated: Annotated[date, BeforeValidator(transform_null_date_to_max)]
pa_decision: str
pa_req_sub_dt: date
pa_req_rec_dt: date
pa_decision_dt: Annotated[date, BeforeValidator(transform_null_date_to_min)]
pa_decision_exp_dt: Annotated[date, BeforeValidator(transform_null_date_to_max)]
service_cnts: int
svc_render_st: str
mr_count_ind: int
mr_count_st_dt: Annotated[date, BeforeValidator(transform_null_date_to_min)]
mr_count_end_dt: Annotated[date, BeforeValidator(transform_null_date_to_max)]
rrb_excl_ind: Annotated[str, BeforeValidator(transform_default_string)]
# TODO: ask if we need these ts sine same as header table
Comment thread
clewellyn-nava marked this conversation as resolved.
Outdated
idr_insrt_ts: Annotated[datetime, {BATCH_TIMESTAMP: True, INSERT_EXCLUDE: True}]
idr_updt_ts: Annotated[
datetime,
{UPDATE_TIMESTAMP: True, INSERT_EXCLUDE: True},
BeforeValidator(transform_null_date_to_min),
]

@override
@staticmethod
def table() -> str:
return "idr.prior_auth_item"

@override
@staticmethod
def last_updated_date_column() -> list[str]:
return []

@override
@staticmethod
def model_type() -> ModelType:
return ModelType.PRIOR_AUTH

@override
@classmethod
def fetch_query(cls, partition: LoadPartition, start_time: datetime, source: Source) -> str:
# Prior auth data older than the lookback period should be filtered
prior_auth = ALIAS_PRIOR_AUTH
return f"""
WITH distinct_prior_auths AS (
SELECT *, ROW_NUMBER()
OVER (PARTITION BY mbi_num, utn, current_segment ORDER BY mbi_num) as row_order
Comment thread
mel1-G marked this conversation as resolved.
FROM {IDR_PRIOR_AUTH_TABLE}
WHERE pa_req_rec_dt > '{MIN_PRIOR_AUTH_LOAD_DATE}'
)
SELECT {{COLUMNS}} FROM distinct_prior_auths {prior_auth}
{{WHERE_CLAUSE}} AND row_order = 1;
"""
Loading
Loading