Skip to content
Merged
2 changes: 1 addition & 1 deletion app/api/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
{
Expand Down
17 changes: 17 additions & 0 deletions app/api/mapping/migrations/0015_scanreporttable_death_table.py
Original file line number Diff line number Diff line change
@@ -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),
),
]
1 change: 1 addition & 0 deletions app/api/mapping/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
26 changes: 19 additions & 7 deletions app/api/services/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"procedure_occurrence",
"device_exposure",
"specimen",
"death",
]

# look up of date-events in all the allowed (destination) tables
Expand All @@ -38,6 +39,7 @@
"device_exposure_end_datetime",
],
"specimen": ["specimen_datetime"],
"death": ["death_datetime"],
}


Expand Down Expand Up @@ -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

Expand All @@ -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:
Expand Down Expand Up @@ -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):
Expand All @@ -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":
Expand Down