Skip to content

Commit

Permalink
Add AllergyIntolerance support to core study
Browse files Browse the repository at this point in the history
- Adds core__allergyintolerance
- Adds core__count_allergyintolerance_month
  • Loading branch information
mikix committed Aug 26, 2024
1 parent b8c3f5f commit 69a194c
Show file tree
Hide file tree
Showing 33 changed files with 1,634 additions and 133 deletions.
14 changes: 14 additions & 0 deletions cumulus_library/.sqlfluff
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,20 @@ remote_location = s3://bucket/study/data/
schema_name = test_schema
schema =
{
'allergyintolerance': {
'category': True,
'criticality': True,
'encounter': {
'reference': True,
},
'id': True,
'patient': {
'reference': True,
},
'reaction': {
'severity': True,
},
},
'condition': {
'category': {
'coding': True, 'code': True, 'display': True, 'system': True, 'userSelected': True, 'version': True, 'text': True
Expand Down
2 changes: 1 addition & 1 deletion cumulus_library/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
from .study_manifest import StudyManifest

__all__ = ["BaseTableBuilder", "CountsBuilder", "StudyConfig", "StudyManifest"]
__version__ = "3.0.0"
__version__ = "3.1.0"
32 changes: 30 additions & 2 deletions cumulus_library/statistics/counts.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ def get_count_query(
"where_clauses",
"fhir_resource",
"filter_resource",
"patient_link",
]:
raise errors.CountsBuilderError(f"count_query received unexpected key: {key}")
return counts_templates.get_count_query(table_name, source_table, table_cols, **kwargs)
Expand All @@ -88,6 +89,33 @@ def get_count_query(
# We're not trying to be overly clever about this to persist the docstrings as the
# primary interface that study authors would see when using these functions.

def count_allergyintolerance(
self,
table_name: str,
source_table: str,
table_cols: list,
where_clauses: list | None = None,
min_subject: int = 10,
) -> str:
"""wrapper method for constructing allergyintolerance counts tables
:param table_name: The name of the table to create. Must start with study prefix
:param source_table: The table to create counts data from
:param table_cols: The columns from the source table to add to the count table
:param where_clauses: An array of where clauses to use for filtering the data
:param min_subject: An integer setting the minimum bin size for inclusion
(default: 10)
"""
return self.get_count_query(
table_name,
source_table,
table_cols,
where_clauses=where_clauses,
min_subject=min_subject,
fhir_resource="allergyintolerance",
patient_link="patient_ref",
)

def count_condition(
self,
table_name: str,
Expand All @@ -112,7 +140,7 @@ def count_condition(
where_clauses=where_clauses,
min_subject=min_subject,
fhir_resource="condition",
filter_resource="encounter",
filter_resource=True,
)

def count_documentreference(
Expand All @@ -139,7 +167,7 @@ def count_documentreference(
where_clauses=where_clauses,
min_subject=min_subject,
fhir_resource="documentreference",
filter_resource="encounter",
filter_resource=True,
)

def count_encounter(
Expand Down
21 changes: 10 additions & 11 deletions cumulus_library/statistics/statistics_templates/count.sql.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@
{#- TODO: move secondary/tertiary resource declaration upstream to
statistics/counts.py level -#}
{%- macro secondary_resource(fhir_resource) -%}
{%- if fhir_resource in ('encounter', 'condition') -%}
{%- if fhir_resource in ('allergyintolerance', 'documentreference', 'encounter', 'observation') -%}
{{ fhir_resource }}_ref
{%- elif fhir_resource == 'condition' -%}
{#- count condition by encounter, because they are often duplicated for billing purposes -#}
encounter_ref
{%- elif fhir_resource == 'documentreference' -%}
documentreference_ref
{%- elif fhir_resource == 'observation'-%}
observation_ref
{%- else -%}
{#- implicit null -#}
{%- endif -%}
Expand Down Expand Up @@ -65,7 +64,7 @@ CREATE TABLE {{ table_name }} AS (
{%- if filter_resource %}
filtered_table AS (
SELECT
s.subject_ref,
s.{{ patient_link }},
{%- if secondary %}
s.{{ secondary }},
{%- endif -%}
Expand Down Expand Up @@ -102,19 +101,19 @@ CREATE TABLE {{ table_name }} AS (
{%- endif -%}

{#- resource specific filtering conditions -#}
{%- if fhir_resource == 'documentreference' and filter_resource %}
{%- if fhir_resource == 'documentreference' %}
WHERE (s.status = 'current')
AND (s.docStatus IS null OR s.docStatus IN ('final', 'amended'))
{%- elif fhir_resource == 'encounter' and filter_resource %}
{%- elif fhir_resource == 'encounter' %}
WHERE s.status = 'finished'
{%- elif fhir_resource == 'observation' and filter_resource %}
{%- elif fhir_resource == 'observation' %}
WHERE (s.status = 'final' OR s.status= 'amended')
{%- endif %}
),
{% endif %}
null_replacement AS (
SELECT
subject_ref,
{{ patient_link }},
{%- if secondary %}
{{ secondary }},
{%- endif -%}
Expand Down Expand Up @@ -177,7 +176,7 @@ CREATE TABLE {{ table_name }} AS (

powerset AS (
SELECT
count(DISTINCT subject_ref) AS cnt_subject_ref,
count(DISTINCT {{ patient_link }}) AS cnt_subject_ref,
{{-cols_delineated_list(table_cols, fhir_resource)}},
concat_ws(
'-',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class CountableFhirResource(Enum):
ids.
"""

ALLERGYINTOLERANCE = "allergyintolerance"
CONDITION = "condition"
DOCUMENTREFERENCE = "documentreference"
ENCOUNTER = "encounter"
Expand All @@ -40,6 +41,7 @@ def get_count_query(
where_clauses: list | None = None,
fhir_resource: str | None = None,
filter_resource: bool | None = True,
patient_link: str = "subject_ref",
) -> str:
"""Generates count tables for generating study outputs"""
path = Path(__file__).parent
Expand Down Expand Up @@ -67,6 +69,7 @@ def get_count_query(
where_clauses=where_clauses,
fhir_resource=fhir_resource,
filter_resource=filter_resource,
patient_link=patient_link,
)
# workaround for conflicting sqlfluff enforcement
return query.replace("-- noqa: disable=LT02\n", "")
66 changes: 66 additions & 0 deletions cumulus_library/studies/core/builder_allergyintolerance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import cumulus_library
from cumulus_library.studies.core.core_templates import core_templates
from cumulus_library.template_sql import sql_utils

expected_table_cols = {
"allergyintolerance": {
"id": [],
"type": [],
"category": [],
"criticality": [],
"recordedDate": [],
"patient": sql_utils.REFERENCE,
"encounter": sql_utils.REFERENCE,
"reaction": ["severity"],
}
}


class CoreAllergyIntoleranceBuilder(cumulus_library.BaseTableBuilder):
display_text = "Creating AllergyIntolerance tables..."

def prepare_queries(self, *args, config: cumulus_library.StudyConfig, **kwargs):
code_sources = [
sql_utils.CodeableConceptConfig(
source_table="allergyintolerance",
column_hierarchy=[("clinicalStatus", dict)],
target_table="core__allergyintolerance_dn_clinical_status",
filter_priority=True,
code_systems=[
# Restrict to just this required binding system
"http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical",
],
),
sql_utils.CodeableConceptConfig(
source_table="allergyintolerance",
column_hierarchy=[("verificationStatus", dict)],
target_table="core__allergyintolerance_dn_verification_status",
filter_priority=True,
code_systems=[
# Restrict to just this required binding system
"http://terminology.hl7.org/CodeSystem/allergyintolerance-verification",
],
),
sql_utils.CodeableConceptConfig(
source_table="allergyintolerance",
column_hierarchy=[("code", dict)],
target_table="core__allergyintolerance_dn_code",
),
sql_utils.CodeableConceptConfig(
source_table="allergyintolerance",
column_hierarchy=[("reaction", list), ("substance", dict)],
target_table="core__allergyintolerance_dn_reaction_substance",
expected={"substance": sql_utils.CODEABLE_CONCEPT},
),
sql_utils.CodeableConceptConfig(
source_table="allergyintolerance",
column_hierarchy=[("reaction", list), ("manifestation", list)],
target_table="core__allergyintolerance_dn_reaction_manifestation",
expected={"manifestation": sql_utils.CODEABLE_CONCEPT},
),
]
self.queries += sql_utils.denormalize_complex_objects(config.db, code_sources)
validated_schema = sql_utils.validate_schema(config.db, expected_table_cols)
self.queries.append(
core_templates.get_core_template("allergyintolerance", validated_schema)
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
{% import 'core_utils.jinja' as utils %}
{% import 'unnest_utils.jinja' as unnest_utils %}

CREATE TABLE core__allergyintolerance AS
WITH temp_allergyintolerance AS (
SELECT
{{-
utils.basic_cols(
'allergyintolerance',
'a',
[
'id',
]
)
}},
{{-
utils.nullable_cols(
'allergyintolerance',
'a',
[
'type',
'category',
'criticality',
('patient', 'reference', 'patient_ref'),
('encounter', 'reference', 'encounter_ref'),
],
schema
)
}},
{{-
utils.date_cols_from_str(
'allergyintolerance',
'a',
['recordedDate'],
schema
)
}},
{{-
utils.truncate_date_cols(
'allergyintolerance',
'a',
[
('recordedDate', 'week'),
('recordedDate', 'month'),
('recordedDate', 'year'),
],
schema
)
}}
FROM allergyintolerance AS a
),

temp_category AS (
SELECT
a.id,
t.category
FROM
allergyintolerance AS a,
UNNEST(a.category) AS t (category)
),

flattened_reaction AS ({{ unnest_utils.flatten('allergyintolerance', 'reaction') }}),

temp_reaction AS (
SELECT
r.id,
r.row,
dn_subs.code AS substance_code,
dn_subs.system AS substance_system,
dn_subs.display AS substance_display,
dn_man.code AS manifestation_code,
dn_man.system AS manifestation_system,
dn_man.display AS manifestation_display,
{{-
utils.nullable_cols(
'allergyintolerance',
'r',
[
('reaction', 'severity', 'severity'),
],
schema
)
}}
FROM flattened_reaction AS r
LEFT JOIN core__allergyintolerance_dn_reaction_substance AS dn_subs
ON r.id = dn_subs.id AND r.row = dn_subs.row
LEFT JOIN core__allergyintolerance_dn_reaction_manifestation AS dn_man
ON r.id = dn_man.id AND r.row = dn_man.row
)

SELECT
ta.id,
CONCAT('AllergyIntolerance/', ta.id) AS allergyintolerance_ref,

{#- We don't expose system for these next two since we filter
down to a single system in the denormalization table #}
dn_cstat.code AS clinicalStatus_code,
dn_vstat.code AS verificationStatus_code,

{#- type, category, and criticality are not in US Core.
But they are useful for clinical interpretation. #}
ta.type,
tcat.category,
ta.criticality,

dn_code.code AS code_code,
dn_code.system AS code_system,
dn_code.display AS code_display,

ta.patient_ref,
ta.encounter_ref,

{#- recordedDate is not in US Core.
But it's useful for looking at only a study period of data. #}
ta.recordedDate,
ta.recordedDate_week,
ta.recordedDate_month,
ta.recordedDate_year,

{#- reaction.substance and reaction.severity are not in US Core.
But they are useful for clinical interpretation. #}
tr.row AS reaction_row,
tr.substance_code AS reaction_substance_code,
tr.substance_system AS reaction_substance_system,
tr.substance_display AS reaction_substance_display,
tr.manifestation_code AS reaction_manifestation_code,
tr.manifestation_system AS reaction_manifestation_system,
tr.manifestation_display AS reaction_manifestation_display,
tr.severity AS reaction_severity

FROM temp_allergyintolerance AS ta
LEFT JOIN temp_reaction AS tr ON ta.id = tr.id
LEFT JOIN core__allergyintolerance_dn_code AS dn_code ON ta.id = dn_code.id
LEFT JOIN core__allergyintolerance_dn_clinical_status AS dn_cstat ON ta.id = dn_cstat.id
LEFT JOIN core__allergyintolerance_dn_verification_status AS dn_vstat
ON ta.id = dn_vstat.id
LEFT JOIN temp_category AS tcat ON ta.id = tcat.id
WHERE ta.recordedDate BETWEEN DATE('2016-01-01') AND CURRENT_DATE;
Loading

0 comments on commit 69a194c

Please sign in to comment.