diff --git a/app/api/api/views.py b/app/api/api/views.py index 97400ac52..fccfff97b 100644 --- a/app/api/api/views.py +++ b/app/api/api/views.py @@ -1101,7 +1101,7 @@ def post(self, request, *args, **kwargs): status=status.HTTP_400_BAD_REQUEST, ) # validate the destination_table - destination_table = _find_destination_table(concept) + destination_table = _find_destination_table(concept, table) if destination_table is None: return Response( { diff --git a/app/api/mapping/migrations/0015_scanreporttable_death_table.py b/app/api/mapping/migrations/0015_scanreporttable_death_table.py new file mode 100644 index 000000000..1cfa08f6a --- /dev/null +++ b/app/api/mapping/migrations/0015_scanreporttable_death_table.py @@ -0,0 +1,17 @@ +# Generated by Django 5.2.8 on 2026-02-23 10:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("mapping", "0014_alter_scanreportfield_name_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="scanreporttable", + name="death_table", + field=models.BooleanField(default=False, null=True), + ), + ] diff --git a/app/api/mapping/models.py b/app/api/mapping/models.py index 72fa7c6e2..e391c629a 100644 --- a/app/api/mapping/models.py +++ b/app/api/mapping/models.py @@ -269,6 +269,7 @@ class ScanReportTable(BaseModel): scan_report = models.ForeignKey(ScanReport, on_delete=models.CASCADE) name = models.CharField(max_length=256, db_index=True) + death_table = models.BooleanField(default=False, null=True) # Quick notes: # - "ScanReportField", instead of ScanReportField, diff --git a/app/api/services/rules.py b/app/api/services/rules.py index e7400f5ce..0a5efad0d 100644 --- a/app/api/services/rules.py +++ b/app/api/services/rules.py @@ -23,6 +23,7 @@ "procedure_occurrence", "device_exposure", "specimen", + "death", ] # look up of date-events in all the allowed (destination) tables @@ -38,6 +39,7 @@ "device_exposure_end_datetime", ], "specimen": ["specimen_datetime"], + "death": ["death_datetime"], } @@ -279,7 +281,9 @@ def _get_date_rules( return date_rules -def _find_destination_table(concept: Concept) -> Optional[OmopTable]: +def _find_destination_table( + concept: Concept, table: ScanReportTable +) -> Optional[OmopTable]: """ Get the destination table for a given Concept @@ -291,10 +295,13 @@ def _find_destination_table(concept: Concept) -> Optional[OmopTable]: """ domain = concept.domain_id.lower() # get the omop field for the source_concept_id for this domain - # if the domain is "meas value" then point directly to its field and table - if domain == "meas value": + + # For death tables (not gender, race, ethnicity) use cause_source_concept_id + if table.death_table and domain not in ["gender", "race", "ethnicity"]: + omop_field = _get_omop_field("cause_source_concept_id", "death") + elif domain == "meas value": omop_field = _get_omop_field("value_as_concept_id", "measurement") - # if the domain is "specimen" or "spec anatomic site" then point directly to one field in the table "specimen" + # if the domain is "specimen" or "spec anatomic site" then point directly to one field in the table "specimen" elif domain == "specimen" or domain == "spec anatomic site": omop_field = _get_omop_field("specimen_concept_id", "specimen") else: @@ -337,13 +344,14 @@ def save_mapping_rules( # Convert "spec anatomic site" to "anatomic_site" to match the name in OMOP CDM if domain == "spec anatomic site": domain = "anatomic_site" + + source_table = source_field.scan_report_table + # start looking up what table we're looking at - destination_table = _find_destination_table(concept) + destination_table = _find_destination_table(concept, source_table) if destination_table is None: return [] - source_table = source_field.scan_report_table - # check whether the person_id and date events for this table are valid # if not, we dont want to create any rules for this concept if not _validate_person_id_and_date(source_table): @@ -360,6 +368,10 @@ def save_mapping_rules( ) rules += date_rules + # Convert domain of concepts added to Death table to "CAUSE", in order to facilitate the get OMOP field process + if source_table.death_table and domain not in ["gender", "race", "ethnicity"]: + domain = "cause" + # In case of domain = "meas value", this rule will not be generated. # And because of the conversion of domain in the line after "rules.append(rule_domain_value_as_concept_id)", this block needs to be upfront if domain == "measurement":