-
Notifications
You must be signed in to change notification settings - Fork 40
Expand file tree
/
Copy pathgen_dd.py
More file actions
222 lines (198 loc) · 8.89 KB
/
Copy pathgen_dd.py
File metadata and controls
222 lines (198 loc) · 8.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
import json
import os
import subprocess
from pathlib import Path
import pandas as pd
import yaml
print("Generating Data Dictionary")
dd_support_folder = "./dictionary-support-files"
structure_def_folder = "./sushi/fsh-generated/resources"
"""
This data structure will get more complex as BFD adds more "types" of data. We're effectively trying
to populate a minimum spanning tree.
We want to have >=1 potential example to populate for each row in the data dictionary.
In practice, Patient will be the least complex (no dependent variables), followed by Coverage
(1 dependent variable), and then EOB (2 dependent variables).
For the initial version, we'll start simple and hard code no dependent variables for each of those.
"""
sample_sources = {
"Patient": "out/Patient.json",
"ExplanationOfBenefit": "out/ExplanationOfBenefit.json",
"ExplanationOfBenefit-Pharmacy": "out/ExplanationOfBenefit-Pharmacy.json",
"Coverage": "out/Coverage-FFS.json",
}
sample_sources_by_profile = {
"PartA": "out/Coverage-FFS.json",
"PartB": "out/Coverage-FFS-PartB.json",
"PartC": "out/Coverage-PartC.json",
"PartD": "out/Coverage-PartD.json",
"DUAL": "out/Coverage-Dual.json",
"Inpatient": "out/ExplanationOfBenefit.json",
"SNF": "out/ExplanationOfBenefit-SNF.json",
"HHA": "out/ExplanationOfBenefit-HHA.json",
"Hospice": "out/ExplanationOfBenefit-Hospice.json",
"Outpatient": "out/ExplanationOfBenefit-Outpatient.json",
"Carrier": "out/ExplanationOfBenefit-Carrier.json",
"DME": "out/ExplanationOfBenefit-DME.json",
"Pharmacy": "out/ExplanationOfBenefit-Pharmacy.json",
"Patient": "out/Patient.json"
}
sample_resources_by_profile = {}
dd_df = []
structure_def_names_descriptions = {}
for resource_type in sample_sources_by_profile:
with Path(sample_sources_by_profile[resource_type]).open() as file:
sample_resources_by_profile[resource_type] = json.load(file)
for walk_info in os.walk(structure_def_folder):
files = list(
filter(lambda file: ".json" in file and "StructureDefinition" in file, walk_info[2])
)
for file_name in files:
with Path(structure_def_folder + "/" + file_name).open() as file:
test_resource = json.load(file)
if test_resource.get("kind") != "logical":
continue
for element in test_resource["differential"]["element"]:
structure_def_names_descriptions[element["id"]] = {}
structure_def_names_descriptions[element["id"]]["name"] = element["short"]
if "definition" in element:
structure_def_names_descriptions[element["id"]]["definition"] = element[
"definition"
]
coverage_parts = ["PartA", "PartB", "PartC", "PartD", "DUAL"]
claim_profiles = ["HHA", "Hospice", "SNF", "DME", "Carrier", "Inpatient", "Outpatient", "Pharmacy"]
for walk_info in os.walk(dd_support_folder):
files = list(filter(lambda file: ".yaml" in file, walk_info[2]))
for file_name in files:
with Path(str(dd_support_folder) + "/" + str(file_name)).open() as file:
data = yaml.safe_load(file)
current_resource_type = file_name[0 : len(file_name) - 5]
for entry in data:
if entry.get("suppressInDD"):
continue
if "fhirPath" in entry:
entry["appliesTo"].sort()
if "sources" in entry:
entry["sources"].sort()
if "Patient" in entry["appliesTo"]:
entry["FHIR Resource"] = "Patient"
elif any(x in coverage_parts for x in entry["appliesTo"]):
entry["FHIR Resource"] = "Coverage"
entry["Coverage / Claim Type"] = entry["appliesTo"]
elif any(x in claim_profiles for x in entry["appliesTo"]):
entry["FHIR Resource"] = "ExplanationOfBenefit"
entry["Coverage / Claim Type"] = entry["appliesTo"]
# This opportunistically populates examples based upon the samples created from executing FML
result = subprocess.run(
[
"node",
"eval_fhirpath.js",
json.dumps(sample_resources_by_profile[entry["appliesTo"][0]]),
entry["fhirPath"],
],
cwd=os.path.dirname(__file__),
check=True,
stdout=subprocess.PIPE,
)
entry["example"] = json.loads(result.stdout)
if "iif" in entry["fhirPath"] or "union" in entry["fhirPath"]:
pass
elif len(entry["example"]) > 0:
entry["example"] = entry["example"][0]
else:
entry["example"] = ""
source_view = entry.get("sourceView")
source_column = entry.get("sourceColumn")
# Populate the element names + missing descriptions
if entry["inputPath"] in structure_def_names_descriptions:
entry["Field Name"] = structure_def_names_descriptions[
entry["inputPath"]
]["name"]
if "definition" in structure_def_names_descriptions[entry["inputPath"]]:
entry["Description"] = structure_def_names_descriptions[
entry["inputPath"]
]["definition"]
#nameOverride and definitionOverride only exist when a field is derived IN fml.
if "nameOverride" in entry:
entry["Field Name"] = entry["nameOverride"]
entry["Description"] = entry["definitionOverride"]
elif "Description" not in entry or not entry["Description"]:
raise ValueError(
f"Entry {entry.get("inputPath", 'Unknown')} has no definition. "
)
entry.pop("inputPath")
dd_df.append(entry)
dd_df = pd.DataFrame(dd_df)
def replace_str(input_str):
# Yes, the below is intentional.
if input_str == input_str and len(str(input_str)) > 0:
return "https://bluebutton.cms.gov/fhir/CodeSystem/" + str(input_str).replace("_", "-")
return ""
dd_df["referenceTable"] = list(map(replace_str, dd_df["referenceTable"]))
dd_df.to_csv(
"out/bfd_data_dictionary.csv",
columns=[
"Field Name",
"Description",
"FHIR Resource",
"Coverage / Claim Type",
"fhirPath",
"example",
"notes",
"sourceView",
"sourceColumn",
"bfdDerived",
"sources",
"referenceTable",
"cclfMapping",
"ccwMapping"
],
)
export_columns = [
"Field Name",
"Description",
"FHIR Resource",
"Coverage / Claim Type",
"fhirPath",
"example",
"notes",
"sourceView",
"sourceColumn",
"bfdDerived",
"sources",
"referenceTable",
"cclfMapping",
"ccwMapping"
]
export_df = dd_df[export_columns]
tips_df = pd.read_csv(dd_support_folder + "/tips.csv")
with pd.ExcelWriter("out/bfd_data_dictionary.xlsx", engine="xlsxwriter") as writer:
export_df.to_excel(writer, sheet_name="Data Dictionary", index=True)
workbook = writer.book
worksheet = writer.sheets["Data Dictionary"]
header_format = workbook.add_format({"bold": True, "bg_color": "#DCE6F2", "border": 1})
text_format = workbook.add_format({"border": 1})
worksheet.write(0, 0, "Row", header_format)
for col_num, value in enumerate(export_df.columns, start=1):
worksheet.write(0, col_num, value, header_format)
worksheet.set_column("A:A", 4, text_format)
worksheet.set_column("B:B", 30, text_format)
worksheet.set_column("C:C", 65, text_format)
worksheet.set_column("D:D", 20, text_format)
worksheet.set_column("E:E", 20, text_format)
worksheet.set_column("F:F", 55, text_format)
worksheet.set_column("G:G", 25, text_format)
worksheet.set_column("H:H", 25, text_format)
worksheet.set_column("I:I", 18, text_format)
worksheet.set_column("J:J", 20, text_format)
worksheet.set_column("K:K", 5, text_format)
worksheet.set_column("L:L", 10, text_format)
worksheet.set_column("M:M", 20, text_format)
worksheet.set_column("N:N", 30, text_format)
worksheet.set_column("O:O", 30, text_format)
tips_df.to_excel(writer, sheet_name="Tips and Tricks", index=True)
worksheet = writer.sheets["Tips and Tricks"]
worksheet.set_column("A:A", 4, text_format)
worksheet.set_column("B:B", 30, text_format)
worksheet.set_column("C:C", 100, text_format)
print("Completed generating data dictionary")