-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
core: refactor medication tables to use modern denormalization
core__medication: - Template cleanup to use standard denorm tables rather than custom extraction code. - If contained resources provide actual codes, we pull them out now (previously we only looked at the contained Concept.text) core__medicationrequest: - No longer ignores rows without dosageInstruction.text General CodeableConcept denormalization: - Add userSelected boolean field to all denorm tables - Fall back from Coding.display to Concept.text - If no codings but Concept.text exists, insert a row with a NULL code and system, but display = Concept.text - Allow the Python code to request extra fields to include along with the coding information (used for capturing contained.id fields) Misc: - Depend on cumulus-fhir-support 1.1 for its contained schema support
- Loading branch information
Showing
27 changed files
with
5,541 additions
and
2,928 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,134 +1,68 @@ | ||
""" Module for generating core medication table""" | ||
|
||
from cumulus_library import base_table_builder, base_utils, databases | ||
from cumulus_library import base_table_builder, databases | ||
from cumulus_library.studies.core.core_templates import core_templates | ||
from cumulus_library.template_sql import base_templates, sql_utils | ||
from cumulus_library.template_sql import sql_utils | ||
|
||
expected_table_cols = { | ||
"medicationrequest": { | ||
"id": [], | ||
"subject": sql_utils.REFERENCE, | ||
"encounter": sql_utils.REFERENCE, | ||
"medicationReference": sql_utils.REFERENCE, | ||
} | ||
} | ||
|
||
|
||
class MedicationBuilder(base_table_builder.BaseTableBuilder): | ||
display_text = "Creating Medication table..." | ||
|
||
def _check_data_in_fields(self, cursor, parser, schema: str): | ||
"""Validates whether either observed medication source is present | ||
We opt to not use the core_templates.utils based version of | ||
checking for data fields, since Medication can come in from | ||
a few different sources - the format is unique to this FHIR | ||
resource. | ||
""" | ||
|
||
data_types = { | ||
"inline": False, | ||
"by_contained_ref": False, | ||
"by_external_ref": False, | ||
} | ||
|
||
table = "medicationrequest" | ||
inline_col = "medicationcodeableconcept" | ||
with base_utils.get_progress_bar(transient=True) as progress: | ||
task = progress.add_task( | ||
"Detecting available medication sources...", | ||
total=3, | ||
) | ||
|
||
# inline medications from FHIR medication | ||
data_types["inline"] = sql_utils.is_field_populated( | ||
schema=schema, | ||
source_table=table, | ||
hierarchy=[(inline_col, dict), ("coding", list)], | ||
cursor=cursor, | ||
parser=parser, | ||
) | ||
if data_types["inline"]: | ||
query = base_templates.get_column_datatype_query( | ||
schema, table, [inline_col] | ||
) | ||
cursor.execute(query) | ||
progress.advance(task) | ||
if "userselected" not in str(cursor.fetchone()[0]): | ||
has_userselected = False | ||
else: | ||
has_userselected = True | ||
else: | ||
has_userselected = False | ||
progress.advance(task) | ||
# Validating presence of FHIR medication requests | ||
if not sql_utils.is_field_populated( | ||
schema=schema, | ||
source_table=table, | ||
hierarchy=[("medicationreference", dict), ("reference", dict)], | ||
expected=sql_utils.REFERENCE, | ||
cursor=cursor, | ||
parser=parser, | ||
): | ||
return data_types, has_userselected | ||
|
||
# checking med ref contents for our two linkage cases | ||
query = base_templates.get_is_table_not_empty_query( | ||
"medicationrequest", | ||
"medicationreference.reference", | ||
conditions=["medicationreference.reference LIKE '#%'"], | ||
) | ||
cursor.execute(query) | ||
progress.advance(task) | ||
if cursor.fetchone() is not None: | ||
data_types["by_contained_ref"] = True | ||
query = base_templates.get_is_table_not_empty_query( | ||
"medicationrequest", | ||
"medicationreference.reference", | ||
conditions=["medicationreference.reference LIKE 'Medication/%'"], | ||
) | ||
cursor.execute(query) | ||
progress.advance(task) | ||
if cursor.fetchone() is not None: | ||
data_types["by_external_ref"] = True | ||
|
||
return data_types, has_userselected | ||
|
||
def prepare_queries( | ||
self, | ||
cursor: databases.DatabaseCursor, | ||
schema: str, | ||
parser: databases.DatabaseParser = None, | ||
*args, | ||
**kwargs, | ||
) -> dict: | ||
) -> None: | ||
"""Constructs queries related to condition codeableConcept | ||
:param cursor: A database cursor object | ||
:param schema: the schema/db name, matching the cursor | ||
:param parser: A database parser | ||
""" | ||
medication_datasources, has_userselected = self._check_data_in_fields( | ||
cursor, parser, schema | ||
code_sources = [ | ||
sql_utils.CodeableConceptConfig( | ||
source_table="medication", | ||
column_hierarchy=[("code", dict)], | ||
target_table="core__medication_dn_code", | ||
), | ||
sql_utils.CodeableConceptConfig( | ||
source_table="medicationrequest", | ||
column_hierarchy=[("medicationCodeableConcept", dict)], | ||
target_table="core__medicationrequest_dn_inline_code", | ||
), | ||
sql_utils.CodeableConceptConfig( | ||
source_table="medicationrequest", | ||
column_hierarchy=[("contained", list), ("code", dict)], | ||
target_table="core__medicationrequest_dn_contained_code", | ||
expected={ | ||
"code": sql_utils.CODEABLE_CONCEPT, | ||
"id": {}, | ||
"resourceType": {}, | ||
}, | ||
extra_fields=[ | ||
("id", "contained_id"), | ||
("resourceType", "resource_type"), | ||
], | ||
), | ||
] | ||
self.queries += sql_utils.denormalize_complex_objects( | ||
schema, cursor, parser, code_sources | ||
) | ||
validated_schema = sql_utils.validate_schema( | ||
cursor, schema, expected_table_cols, parser | ||
) | ||
if ( | ||
medication_datasources["inline"] | ||
or medication_datasources["by_contained_ref"] | ||
or medication_datasources["by_external_ref"] | ||
): | ||
self.queries.append( | ||
core_templates.get_core_template( | ||
"medication", | ||
config={ | ||
"medication_datasources": medication_datasources, | ||
"has_userselected": has_userselected, | ||
}, | ||
) | ||
) | ||
else: | ||
self.queries.append( | ||
base_templates.get_ctas_empty_query( | ||
schema, | ||
"core__medication", | ||
[ | ||
"id", | ||
"encounter_ref", | ||
"patient_ref", | ||
"code", | ||
"display", | ||
"code_system", | ||
"userselected", | ||
], | ||
) | ||
) | ||
self.queries += [ | ||
core_templates.get_core_template("medication", validated_schema), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.