Skip to content

Commit 28c240a

Browse files
merge
2 parents 0b99e22 + 0b88f42 commit 28c240a

64 files changed

Lines changed: 5114 additions & 5793 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci-python.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ jobs:
5858
uses: astral-sh/setup-uv@v5
5959
with:
6060
# Install a specific version of uv.
61-
version: "0.7.8"
61+
version: "0.8.10"
6262
- name: Run Python Tests per project
6363
id: run-pytest-set
6464
run: |

apps/bfd-model/bfd-model-idr/dictionary-support-files/ExplanationOfBenefit.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1612,7 +1612,7 @@
16121612
sources:
16131613
- NCH
16141614
- VIPS
1615-
sourceView: V2_MDCR_CLM
1615+
sourceView: V2_MDCR_CLM_LINE
16161616
sourceColumn: CLM_RNDRG_PRVDR_TAX_NUM
16171617
fhirPath: ExplanationOfBenefit.contained.where(id = %root.provider.reference.substring(1)).identifier.where(system='urn:oid:2.16.840.1.113883.4.4').value
16181618
notes: This variable comes in on a claim line, but we populate it on a practitioner resource.

apps/bfd-model/bfd-model-idr/sample-data/generator/claims_generator.py

Lines changed: 66 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,29 @@
1919

2020
def save_output_files(clm,clm_line,clm_val,clm_dt_sgntr,clm_prod,clm_instnl,clm_line_instnl,clm_dcmtn,clm_fiss,clm_prfnl,clm_line_prfnl,clm_line_rx):
2121
Path("out").mkdir(exist_ok=True)
22+
2223
df = pd.json_normalize(clm)
2324
df['CLM_BLOOD_PT_FRNSH_QTY'] = df['CLM_BLOOD_PT_FRNSH_QTY'].astype('Int64')
25+
df['CLM_BLG_PRVDR_OSCAR_NUM'] = df['CLM_BLG_PRVDR_OSCAR_NUM'].astype('string')
26+
# Columns you want as string without decimal/nan
27+
int_to_string_cols = [
28+
'CLM_TYPE_CD', 'CLM_NUM_SK', 'PRVDR_PRSCRBNG_PRVDR_NPI_NUM',
29+
'PRVDR_RFRG_PRVDR_NPI_NUM', 'PRVDR_BLG_PRVDR_NPI_NUM',
30+
'CLM_ATNDG_PRVDR_NPI_NUM', 'CLM_OPRTG_PRVDR_NPI_NUM',
31+
'CLM_OTHR_PRVDR_NPI_NUM', 'CLM_RNDRG_PRVDR_NPI_NUM'
32+
]
33+
34+
for col in int_to_string_cols:
35+
df[col] = (
36+
df[col]
37+
.astype('Int64') # Handle floats like 1234.0 → 1234
38+
.astype('string') # Pandas nullable string type
39+
.fillna('') # Replace <NA> with empty string
40+
)
2441
df.to_csv('out/SYNTHETIC_CLM.csv', index=False)
42+
2543
df = pd.json_normalize(clm_line)
44+
df['CLM_LINE_NUM'] = df['CLM_LINE_NUM'].astype('str')
2645
df.to_csv('out/SYNTHETIC_CLM_LINE.csv', index=False)
2746
df = pd.json_normalize(clm_val)
2847
df.to_csv('out/SYNTHETIC_CLM_VAL.csv', index=False)
@@ -33,6 +52,7 @@ def save_output_files(clm,clm_line,clm_val,clm_dt_sgntr,clm_prod,clm_instnl,clm_
3352
df = pd.json_normalize(clm_instnl)
3453
df.to_csv('out/SYNTHETIC_CLM_INSTNL.csv', index=False)
3554
df = pd.DataFrame(clm_line_instnl)
55+
df['CLM_LINE_NUM'] = df['CLM_LINE_NUM'].astype('str')
3656
df.to_csv('out/SYNTHETIC_CLM_LINE_INSTNL.csv', index=False)
3757
df = pd.json_normalize(clm_dcmtn)
3858
df.to_csv('out/SYNTHETIC_CLM_DCMTN.csv', index=False)
@@ -41,6 +61,7 @@ def save_output_files(clm,clm_line,clm_val,clm_dt_sgntr,clm_prod,clm_instnl,clm_
4161
df = pd.json_normalize(clm_prfnl)
4262
df.to_csv('out/SYNTHETIC_CLM_PRFNL.csv', index=False)
4363
df = pd.json_normalize(clm_line_prfnl)
64+
df['CLM_LINE_NUM'] = df['CLM_LINE_NUM'].astype('str')
4465
df.to_csv('out/SYNTHETIC_CLM_LINE_PRFNL.csv', index=False)
4566
df = pd.json_normalize(clm_line_rx)
4667
df.to_csv('out/SYNTHETIC_CLM_LINE_RX.csv', index=False)
@@ -241,7 +262,7 @@ def gen_claim(bene_sk = '-1', min_date = '2018-01-01', max_date = str(date.today
241262
claim['CLM']['CLM_ORIG_CNTL_NUM'] = ''.join(random.choices(string.digits, k=14)) + ''.join(random.choices(string.ascii_uppercase,k=3))
242263
claim['CLM']['CLM_PRNT_CNTL_NUM'] = claim['CLM']['CLM_ORIG_CNTL_NUM']
243264

244-
if(claim['CLM']['CLM_TYPE_CD'] in (20,30,40,60,61,62,63,71,72)):
265+
if(clm_type_cd in (20,30,40,60,61,62,63,71,72)):
245266
claim['CLM']['CLM_BLOOD_PT_FRNSH_QTY'] = random.randint(0,20)
246267

247268
claim['CLM']['CLM_NUM_SK'] = 1
@@ -263,21 +284,36 @@ def gen_claim(bene_sk = '-1', min_date = '2018-01-01', max_date = str(date.today
263284
claim['CLM']['CLM_SBMTR_CNTRCT_NUM'] = 'Z0001'
264285
claim['CLM']['CLM_SBMTR_CNTRCT_PBP_NUM'] = random.choice(avail_pbp_nums)
265286
claim_line = {}
287+
claim_line['CLM_UNIQ_ID'] = claim['CLM']['CLM_UNIQ_ID']
288+
claim_line['CLM_NUM_SK'] = claim['CLM']['CLM_NUM_SK']
289+
claim_line['CLM_TYPE_CD'] = claim['CLM']['CLM_TYPE_CD']
290+
claim_line['GEO_BENE_SK'] = claim['CLM']['GEO_BENE_SK']
291+
claim_line['CLM_DT_SGNTR_SK'] = claim['CLM']['CLM_DT_SGNTR_SK']
266292
claim_line['CLM_LINE_CVRD_PD_AMT'] = round(random.uniform(1, 1000000),2)
267293
claim_line['CLM_LINE_NCVRD_PD_AMT'] = round(random.uniform(1, 1000000),2)
294+
claim_line['CLM_LINE_NCVRD_CHRG_AMT'] = round(random.uniform(0,1500),2)
268295
claim_line['CLM_LINE_NDC_CD'] = random.choice(available_ndc)
269296
claim_line['CLM_LINE_SRVC_UNIT_QTY'] = random.randint(1,10)
270-
claim_line['CLM_LINE_FROM_DT'] =claim['CLM']['CLM_FROM_DT']
297+
claim_line['CLM_LINE_FROM_DT'] = claim['CLM']['CLM_FROM_DT']
298+
claim_line['CLM_LINE_THRU_DT'] = claim['CLM']['CLM_THRU_DT']
271299
claim_line['CLM_LINE_NDC_QTY'] = random.randint(1,10)
272300
claim_line['CLM_LINE_NDC_QTY_QLFYR_CD'] = 'ML'
273301
claim_line['CLM_LINE_BENE_PD_AMT'] = round(random.uniform(1, 1000000),2)
274302
claim_line['CLM_LINE_PRVDR_PMT_AMT'] = round(random.uniform(1, 1000000),2)
303+
claim_line['CLM_LINE_SBMT_CHRG_AMT'] = round(random.uniform(0,5),2)
304+
claim_line['CLM_LINE_BENE_PMT_AMT'] = round(random.uniform(0,5),2)
305+
claim_line['CLM_LINE_BLOOD_DDCTBL_AMT'] = round(random.uniform(0,15),2)
306+
claim_line['CLM_LINE_MDCR_DDCTBL_AMT'] = round(random.uniform(0,5),2)
307+
claim_line['CLM_LINE_NUM'] = '1'
308+
claim_line['CLM_FROM_DT'] = claim['CLM']['CLM_FROM_DT']
275309

276310
claim_line_rx = {}
277311
claim_line_rx['CLM_UNIQ_ID'] = claim['CLM']['CLM_UNIQ_ID']
278312
claim_line_rx['CLM_NUM_SK'] = claim['CLM']['CLM_NUM_SK']
279313
claim_line_rx['CLM_TYPE_CD'] = claim['CLM']['CLM_TYPE_CD']
280-
claim_line_rx['CLM_GEO_BENE_SK'] = claim['CLM']['GEO_BENE_SK']
314+
claim_line_rx['GEO_BENE_SK'] = claim['CLM']['GEO_BENE_SK']
315+
claim_line_rx['CLM_LINE_NUM'] = '1'
316+
claim_line_rx['CLM_FROM_DT'] = claim['CLM']['CLM_FROM_DT']
281317
claim_line_rx['CLM_LINE_RX_ORGN_CD'] = random.choice(generator.code_systems['CLM_LINE_RX_ORGN_CD'])
282318
claim_line_rx['CLM_BRND_GNRC_CD'] = random.choice(generator.code_systems['CLM_BRND_GNRC_CD'])
283319
claim_line_rx['CLM_PTNT_RSDNC_CD'] = random.choice(generator.code_systems['CLM_PTNT_RSDNC_CD'])
@@ -327,29 +363,26 @@ def gen_claim(bene_sk = '-1', min_date = '2018-01-01', max_date = str(date.today
327363
if(clm_type_cd in (1,2,3,4,10,20,30,40,50,60,61,62,63,71,72,81,82)):
328364
clm_ltst_clm_ind = 'Y'
329365
claim['CLM']['CLM_LTST_CLM_IND'] = clm_ltst_clm_ind
330-
366+
367+
claim['CLM_DCMTN']['CLM_DT_SGNTR_SK'] = claim['CLM']['CLM_DT_SGNTR_SK']
368+
claim['CLM_DCMTN']['CLM_NUM_SK'] = claim['CLM']['CLM_NUM_SK']
369+
claim['CLM_DCMTN']['GEO_BENE_SK'] = claim['CLM']['GEO_BENE_SK']
370+
claim['CLM_DCMTN']['CLM_TYPE_CD'] = claim['CLM']['CLM_TYPE_CD']
331371

332372
#CLM_RIC_CDs are generally tied to the claim type code.
333-
if(claim['CLM']['CLM_TYPE_CD'] in (20,30,50,60,61,62,63,64)):
373+
if(clm_type_cd in (20,30,50,60,61,62,63,64)):
334374
#part A!
335375
claim['CLM_DCMTN']['CLM_NRLN_RIC_CD'] = 'V' #inpatient
336-
claim['CLM_DCMTN']['CLM_DT_SGNTR_SK'] = claim['CLM']['CLM_DT_SGNTR_SK']
337-
claim['CLM_DCMTN']['CLM_NUM_SK'] = claim['CLM']['CLM_NUM_SK']
338-
claim['CLM_DCMTN']['GEO_BENE_SK'] = claim['CLM']['GEO_BENE_SK']
339-
claim['CLM_DCMTN']['CLM_TYPE_CD'] = claim['CLM']['CLM_TYPE_CD']
340-
elif(claim['CLM']['CLM_TYPE_CD'] == 40):
376+
elif(clm_type_cd == 40):
341377
#outpatient
342378
claim['CLM_DCMTN']['CLM_NRLN_RIC_CD'] = 'W' #outpatient
343-
claim['CLM_DCMTN']['CLM_DT_SGNTR_SK'] = claim['CLM']['CLM_DT_SGNTR_SK']
344-
claim['CLM_DCMTN']['CLM_NUM_SK'] = claim['CLM']['CLM_NUM_SK']
345-
claim['CLM_DCMTN']['GEO_BENE_SK'] = claim['CLM']['GEO_BENE_SK']
346-
claim['CLM_DCMTN']['CLM_TYPE_CD'] = claim['CLM']['CLM_TYPE_CD']
347-
elif(claim['CLM']['CLM_TYPE_CD'] == 10):
379+
elif(clm_type_cd == 10):
348380
claim['CLM_DCMTN']['CLM_NRLN_RIC_CD'] = random.choice(['U','V','W'])
349-
claim['CLM_DCMTN']['CLM_DT_SGNTR_SK'] = claim['CLM']['CLM_DT_SGNTR_SK']
350-
claim['CLM_DCMTN']['CLM_NUM_SK'] = claim['CLM']['CLM_NUM_SK']
351-
claim['CLM_DCMTN']['GEO_BENE_SK'] = claim['CLM']['GEO_BENE_SK']
352-
claim['CLM_DCMTN']['CLM_TYPE_CD'] = claim['CLM']['CLM_TYPE_CD']
381+
elif(clm_type_cd in (71, 72)):
382+
claim['CLM_DCMTN']['CLM_NRLN_RIC_CD'] = 'O'
383+
elif(clm_type_cd in (81, 82)):
384+
claim['CLM_DCMTN']['CLM_NRLN_RIC_CD'] = 'M'
385+
353386
add_meta_timestamps(claim['CLM_DCMTN'], claim['CLM'], max_date)
354387

355388
#provider elements:
@@ -390,7 +423,7 @@ def gen_claim(bene_sk = '-1', min_date = '2018-01-01', max_date = str(date.today
390423

391424
claim['CLM_VAL'] = []
392425
#CLM_OPRTNL_DSPRTNT_AMT + CLM_OPRTNL_IME_AMT
393-
if(claim['CLM']['CLM_TYPE_CD'] in (20,40,60,61,62,63,64)):
426+
if(clm_type_cd in (20,40,60,61,62,63,64)):
394427
#Note, this is a table we'll use sparsely, it appears. I've replaced the 5 key unique identifier with CLM_UNIQ_ID.
395428
clm_val_dsprtnt = {'CLM_DT_SGNTR_SK':claim['CLM']['CLM_DT_SGNTR_SK'],
396429
'CLM_NUM_SK':claim['CLM']['CLM_NUM_SK'],
@@ -524,6 +557,7 @@ def gen_claim(bene_sk = '-1', min_date = '2018-01-01', max_date = str(date.today
524557
claim['CLM_PRFNL']['CLM_MDCR_PRFNL_PRMRY_PYR_AMT'] = round(random.uniform(10,1000),2)
525558
claim['CLM_PRFNL']['CLM_MDCR_PRFNL_PRVDR_ASGNMT_SW'] = random.choice(generator.code_systems['CLM_MDCR_PRFNL_PRVDR_ASGNMT_SW'])
526559
claim['CLM_PRFNL']['CLM_CLNCL_TRIL_NUM'] = str(random.randint(0,10000))
560+
add_meta_timestamps(claim['CLM_PRFNL'], claim['CLM'], max_date)
527561

528562

529563
num_clm_lines = random.randint(1,15)
@@ -539,6 +573,8 @@ def gen_claim(bene_sk = '-1', min_date = '2018-01-01', max_date = str(date.today
539573
claim_line['CLM_TYPE_CD'] = claim['CLM']['CLM_TYPE_CD']
540574
claim_line['CLM_NUM_SK'] = claim['CLM']['CLM_NUM_SK']
541575
claim_line['CLM_FROM_DT'] = claim['CLM']['CLM_FROM_DT']
576+
claim_line['CLM_LINE_FROM_DT'] = claim['CLM']['CLM_FROM_DT']
577+
claim_line['CLM_LINE_THRU_DT'] = claim['CLM']['CLM_THRU_DT']
542578
if(clm_type_cd >=10 and clm_type_cd <= 64):
543579
claim_line_inst['GEO_BENE_SK'] = claim['CLM']['GEO_BENE_SK']
544580
claim_line_inst['CLM_DT_SGNTR_SK'] = claim['CLM']['CLM_DT_SGNTR_SK']
@@ -565,16 +601,13 @@ def gen_claim(bene_sk = '-1', min_date = '2018-01-01', max_date = str(date.today
565601

566602
if(random.randint(0,10)==6):
567603
claim_line_prfnl['CLM_LINE_HCT_HGB_TYPE_CD'] = random.choice(['R1','R2'])
568-
claim_line_prfnl['CLM_LINE_HCT_HGB_TYPE_CD'] = round(random.uniform(0,1),2)
569604
claim_line_prfnl['CLM_LINE_CARR_CLNCL_LAB_NUM'] = random.choice(['11D1111111','22D2222222'])
570605

571606

572607

573608

574609

575610
#these don't have much variance in our synthetic data, but they are not strictly the same in actual data!
576-
claim_line['CLM_LINE_FROM_DT'] = claim['CLM']['CLM_FROM_DT']
577-
claim_line['CLM_LINE_THRU_DT'] = claim['CLM']['CLM_THRU_DT']
578611
claim_line['CLM_LINE_MDCR_COINSRNC_AMT'] = round(random.uniform(0,5),2)
579612

580613
#pick a random diagnosis.
@@ -585,7 +618,7 @@ def gen_claim(bene_sk = '-1', min_date = '2018-01-01', max_date = str(date.today
585618

586619
if(clm_type_cd>=71 and clm_type_cd<=72):
587620

588-
claim_line['CLM_RNDRG_PRVDR_TAX_NUM'] = random.choice(['192834791234','91283472987'])
621+
claim_line['CLM_RNDRG_PRVDR_TAX_NUM'] = random.choice(['1928347912','912834729'])
589622
claim_line['CLM_RNDRG_PRVDR_PIN_NUM'] = random.choice(['29364819','19238747'])
590623
if(random.choice([0,10])==7):
591624
claim_line['CLM_LINE_ANSTHSA_UNIT_CNT'] = random.uniform(0,10)
@@ -598,7 +631,7 @@ def gen_claim(bene_sk = '-1', min_date = '2018-01-01', max_date = str(date.today
598631
claim_line_prfnl['CLM_LINE_PRFNL_DME_PRICE_AMT'] = round(random.uniform(0,10000),2)
599632
claim_line['CLM_RNDRG_PRVDR_NPI_NUM'] = random.choice(type_1_npis)
600633

601-
634+
add_meta_timestamps(claim_line_prfnl, claim['CLM'], max_date)
602635

603636
claim_line['CLM_LINE_HCPCS_CD'] = random.choice(proc_codes_cpt_hcpcs)
604637
num_mods = random.randint(0,5)
@@ -674,23 +707,24 @@ def gen_pac_version_of_claim(claim, max_date):
674707
'''
675708
pac_claim = copy.deepcopy(claim)
676709
pac_claim['CLM']['CLM_UNIQ_ID'] = ''.join(random.choices(string.digits, k=13))
710+
pac_clm_type_cd = int(pac_claim['CLM']['CLM_TYPE_CD'])
677711

678-
if(pac_claim['CLM']['CLM_TYPE_CD'] in (60,61,62,63,64)):
712+
if(pac_clm_type_cd in (60,61,62,63,64)):
679713
pac_claim['CLM']['CLM_TYPE_CD'] = random.choices([1011,2011,1041,2041],weights=[.48,.48,.02,.02])[0]
680714

681-
if(pac_claim['CLM']['CLM_TYPE_CD'] == 40):
715+
if(pac_clm_type_cd == 40):
682716
pac_claim['CLM']['CLM_TYPE_CD'] = random.choices([1013,2013,1071,2071],weights=[.48,.48,.02,.02])[0]
683717

684-
if(pac_claim['CLM']['CLM_TYPE_CD'] == 10):
718+
if(pac_clm_type_cd == 10):
685719
pac_claim['CLM']['CLM_TYPE_CD'] = random.choices([1032,2032,1033,2033],weights=[.48,.48,.02,.02])[0]
686720

687-
if(pac_claim['CLM']['CLM_TYPE_CD'] == 20):
721+
if(pac_clm_type_cd == 20):
688722
pac_claim['CLM']['CLM_TYPE_CD'] = random.choice([1021,2021])
689723

690-
if(pac_claim['CLM']['CLM_TYPE_CD'] == 30):
724+
if(pac_clm_type_cd == 30):
691725
pac_claim['CLM']['CLM_TYPE_CD'] = random.choices([1018,2018])[0]
692726

693-
if(pac_claim['CLM']['CLM_TYPE_CD'] == 50):
727+
if(pac_clm_type_cd == 50):
694728
pac_claim['CLM']['CLM_TYPE_CD'] = random.choices([1081,2081,1082,2082],weights=[.48,.48,.02,.02])[0]
695729

696730
if('CLM_BLOOD_PT_FRNSH_QTY' in pac_claim['CLM']):
@@ -708,6 +742,7 @@ def gen_pac_version_of_claim(claim, max_date):
708742

709743

710744
for i in range(len(pac_claim['CLM_LINE'])):
745+
pac_claim['CLM_LINE'][i]['CLM_LINE_NUM'] = i + 1
711746
pac_claim['CLM_LINE'][i]['CLM_UNIQ_ID'] = pac_claim['CLM']['CLM_UNIQ_ID']
712747
pac_claim['CLM_LINE'][i]['GEO_BENE_SK'] = pac_claim['CLM']['GEO_BENE_SK']
713748
pac_claim['CLM_LINE'][i]['CLM_DT_SGNTR_SK'] = pac_claim['CLM']['CLM_DT_SGNTR_SK']
@@ -920,9 +955,6 @@ def main():
920955
for line in pac_claim['CLM_LINE_INSTNL']:
921956
CLM_LINE_INSTNL.append(line)
922957
CLM_FISS.append(pac_claim['CLM_FISS'])
923-
CLM_PRFNL.append(pac_claim['CLM_PRFNL'])
924-
for line in pac_claim['CLM_LINE_PRFNL']:
925-
CLM_LINE_PRFNL.append(line)
926958

927959
pt_complete+=1
928960
save_output_files(CLM,CLM_LINE,CLM_VAL,CLM_DT_SGNTR,CLM_PROD,CLM_INSTNL,CLM_LINE_INSTNL,CLM_DCMTN,CLM_FISS,CLM_PRFNL,CLM_LINE_PRFNL,CLM_LINE_RX)

apps/bfd-model/bfd-model-idr/sample-data/generator/generator_util.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,20 +114,26 @@ def gen_bene_sk(self):
114114
return bene_sk
115115

116116
def generate_bene_xref(self, new_bene_sk, old_bene_sk):
117+
118+
bene_hicn_num = str(random.randint(1000, 100000000)) + random.choice(string.ascii_letters)
119+
117120
#10% chance for invalid xref.
118121
kill_cred_cd = 1 if random.randint(1, 10) == 1 else 2
119122

120123
efctv_ts = self.fake.date_time_between_dates(
121124
datetime.date(year=2017, month=5, day=20),
122125
datetime.datetime.now() - datetime.timedelta(days=1)
123126
)
127+
src_rec_ctre_ts = self.fake.date_time_between_dates(efctv_ts, datetime.datetime.now() - datetime.timedelta(days=1))
124128
insrt_ts = self.fake.date_time_between_dates(efctv_ts, datetime.datetime.now() - datetime.timedelta(days=1))
125129
updt_ts = self.fake.date_time_between_dates(insrt_ts, datetime.datetime.now() - datetime.timedelta(days=1))
126130

127131
xref_row = {
128132
"BENE_SK": str(new_bene_sk),
129-
"BENE_XREF": str(old_bene_sk),
133+
"BENE_XREF_SK": str(old_bene_sk),
134+
"BENE_HICN_NUM": bene_hicn_num,
130135
"BENE_KILL_CRED_CD": str(kill_cred_cd),
136+
"SRC_REC_CRTE_TS": str(src_rec_ctre_ts),
131137
"IDR_TRANS_EFCTV_TS": str(efctv_ts),
132138
"IDR_INSRT_TS": str(insrt_ts),
133139
"IDR_UPDT_TS": str(updt_ts),

0 commit comments

Comments
 (0)