From b2330d93323cd582078d6095109994e995fa3712 Mon Sep 17 00:00:00 2001 From: Chiara Rasi Date: Wed, 12 Oct 2022 10:47:54 +0200 Subject: [PATCH] Limit incidental findings by filtering matching causatives and managed vars by gene panels - Case page fix - (#3611) * Set gene panels for variant matching at the institite level * Move some code to new function * revert some code * remove debug message * remove a number of debug messages * simplify * Revert some code * simplified * fix a comment * Avoid secondary findings on case page OK * Revert demo config file * remove debug message * update changelog * conceptual fix * updated changelog * Add a comment * Display badge on case page so users will know if safe search is on Co-authored-by: Daniel Nilsson --- CHANGELOG.md | 1 + scout/adapter/mongo/institute.py | 72 +++++++++---------- scout/server/blueprints/cases/controllers.py | 11 ++- .../cases/templates/cases/case.html | 10 ++- .../blueprints/institutes/controllers.py | 9 +++ scout/server/blueprints/institutes/forms.py | 7 +- .../institutes/templates/overview/utils.html | 33 ++++++--- 7 files changed, 92 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e26c6fc7f5..b503bae97b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ About changelog [here](https://keepachangelog.com/en/1.0.0/) ### Added - Mitochondrial deletion signatures (mitosign) can be uploaded and shown with mtDNA report - A `Type of analysis` column on Causatives and Validated variants pages +- List of "safe" gene panels available for matching causatives and managed variants in institute settings, to avoid secondary findings ### Changed - Hide removed gene panels by default in panels page - Removed option for filtering cancer SVs by Tumor and Normal alt AF diff --git a/scout/adapter/mongo/institute.py b/scout/adapter/mongo/institute.py index 6e9b327f70..9fae612451 100644 --- a/scout/adapter/mongo/institute.py +++ b/scout/adapter/mongo/institute.py @@ -43,6 +43,7 @@ def update_institute( remove_sanger=None, phenotype_groups=None, gene_panels=None, + gene_panels_matching=None, group_abbreviations=None, add_groups=None, sharing_institutes=None, @@ -63,6 +64,7 @@ def update_institute( remove_sanger(str): Email adress for sanger user to be removed phenotype_groups(iterable(str)): New phenotype groups gene_panels(dict): a dictionary of panels with key=panel_name and value=display_name + gene_panels_matching(dict): panels to limit search of matching variants (managed, causatives) to. Dict with key=panel_name and value=display_name group_abbreviations(iterable(str)) add_groups(bool): If groups should be added. If False replace groups sharing_institutes(list(str)): Other institutes to share cases with @@ -94,9 +96,6 @@ def update_institute( ) updates["$push"] = {"sanger_recipients": sanger_recipient} - if sanger_recipients is not None: - updates["$set"]["sanger_recipients"] = sanger_recipients # can be empty list - if remove_sanger: LOG.info( "Removing sanger recipient {0} from institute: {1}".format( @@ -105,27 +104,21 @@ def update_institute( ) updates["$pull"] = {"sanger_recipients": remove_sanger} - if coverage_cutoff: - LOG.info( - "Updating coverage cutoff for institute: {0} to {1}".format( - internal_id, coverage_cutoff - ) - ) - updates["$set"]["coverage_cutoff"] = coverage_cutoff - - if frequency_cutoff: - LOG.info( - "Updating frequency cutoff for institute: {0} to {1}".format( - internal_id, frequency_cutoff - ) - ) - updates["$set"]["frequency_cutoff"] = frequency_cutoff - - if display_name: - LOG.info( - "Updating display name for institute: {0} to {1}".format(internal_id, display_name) - ) - updates["$set"]["display_name"] = display_name + # Set a number of items + GENERAL_SETTINGS = { + "cohorts": cohorts, + "collaborators": sharing_institutes, + "coverage_cutoff": coverage_cutoff, + "display_name": display_name, + "frequency_cutoff": frequency_cutoff, + "gene_panels": gene_panels, + "gene_panels_matching": gene_panels_matching, + "loqusdb_id": loqusdb_ids, + "sanger_recipients": sanger_recipients, + } + for key, value in GENERAL_SETTINGS.items(): + if value not in [None, ""]: + updates["$set"][key] = value if phenotype_groups is not None: if group_abbreviations: @@ -145,19 +138,6 @@ def update_institute( existing_groups[hpo_term] = {"name": description, "abbr": abbreviation} updates["$set"]["phenotype_groups"] = existing_groups - if gene_panels is not None: - updates["$set"]["gene_panels"] = gene_panels - - if sharing_institutes is not None: - updates["$set"]["collaborators"] = sharing_institutes - - if cohorts is not None: - updates["$set"]["cohorts"] = cohorts - - if loqusdb_ids: - LOG.warning("Updating loqusdb id for institute: %s to %s", internal_id, loqusdb_ids) - updates["$set"]["loqusdb_id"] = loqusdb_ids - if alamut_key is not None: updates["$set"]["alamut_key"] = ( alamut_key if alamut_key != "" else None @@ -192,6 +172,24 @@ def institute(self, institute_id): return institute_obj + def safe_genes_filter(self, institute_id): + """Returns a list of "safe" HGNC IDs to filter variants with. These genes are retrieved from the institute.gene_panels_matching + Can be used to limit secondary findings when retrieving other causatives or matching managed variants + + Args: + institute_id(str): _id of an institute + + Returns: + safe_genes(list of HGNC ids) + """ + safe_genes = [] + institute_obj = self.institute(institute_id) + if not institute_obj: + return safe_genes # return an empty list + for panel_name in institute_obj.get("gene_panels_matching", {}).keys(): + safe_genes += self.panel_to_genes(panel_name=panel_name, gene_format="hgnc_id") + return safe_genes + def institutes(self, institute_ids=None): """Fetch all institutes. diff --git a/scout/server/blueprints/cases/controllers.py b/scout/server/blueprints/cases/controllers.py index 029996d1d9..5a6f40d8d3 100644 --- a/scout/server/blueprints/cases/controllers.py +++ b/scout/server/blueprints/cases/controllers.py @@ -329,12 +329,19 @@ def case(store, institute_obj, case_obj): for img in case_obj["custom_images"][img_section]: img["data"] = b64encode(img["data"]).decode("utf-8") + # Limit secondary findings according to institute settings + limit_genes = store.safe_genes_filter(institute_obj["_id"]) + data = { "institute": institute_obj, "case": case_obj, "status_class": STATUS_MAP.get(case_obj["status"]), - "other_causatives": [var for var in store.check_causatives(case_obj=case_obj)], - "managed_variants": [var for var in store.check_managed(case_obj=case_obj)], + "other_causatives": [ + var for var in store.check_causatives(case_obj=case_obj, limit_genes=limit_genes) + ], + "managed_variants": [ + var for var in store.check_managed(case_obj=case_obj, limit_genes=limit_genes) + ], "comments": store.events(institute_obj, case=case_obj, comments=True), "hpo_groups": pheno_groups, "case_groups": case_groups, diff --git a/scout/server/blueprints/cases/templates/cases/case.html b/scout/server/blueprints/cases/templates/cases/case.html index 135efc482f..f1f78fac13 100644 --- a/scout/server/blueprints/cases/templates/cases/case.html +++ b/scout/server/blueprints/cases/templates/cases/case.html @@ -54,9 +54,17 @@

Internal ID: {{case._id}}

-
+
{{ variants_buttons() }}
+
+ Safe variant matching: + {% if institute.gene_panels_matching %} + on + {% else %} + off + {% endif %} +
diff --git a/scout/server/blueprints/institutes/controllers.py b/scout/server/blueprints/institutes/controllers.py index 4d289b6d99..75ef73800a 100644 --- a/scout/server/blueprints/institutes/controllers.py +++ b/scout/server/blueprints/institutes/controllers.py @@ -274,6 +274,7 @@ def populate_institute_form(form, institute_obj): for panel in available_panels: panel_set.add((panel["panel_name"], panel["display_name"])) form.gene_panels.choices = sorted(panel_set, key=lambda tup: tup[1]) + form.gene_panels_matching.choices = sorted(panel_set, key=lambda tup: tup[1]) return default_phenotypes @@ -294,6 +295,7 @@ def update_institute_settings(store, institute_obj, form): sharing_institutes = [] phenotype_groups = [] gene_panels = {} + gene_panels_matching = {} group_abbreviations = [] cohorts = [] @@ -317,6 +319,12 @@ def update_institute_settings(store, institute_obj, form): continue gene_panels[panel_name] = panel_obj["display_name"] + for panel_name in form.getlist("gene_panels_matching"): + panel_obj = store.gene_panel(panel_name) + if panel_obj is None: + continue + gene_panels_matching[panel_name] = panel_obj["display_name"] + for cohort in form.getlist("cohorts"): cohorts.append(cohort.strip()) @@ -328,6 +336,7 @@ def update_institute_settings(store, institute_obj, form): display_name=form.get("display_name"), phenotype_groups=phenotype_groups, gene_panels=gene_panels, + gene_panels_matching=gene_panels_matching, group_abbreviations=group_abbreviations, add_groups=False, sharing_institutes=sharing_institutes, diff --git a/scout/server/blueprints/institutes/forms.py b/scout/server/blueprints/institutes/forms.py index e54b1af8bb..f043f461ea 100644 --- a/scout/server/blueprints/institutes/forms.py +++ b/scout/server/blueprints/institutes/forms.py @@ -72,7 +72,12 @@ class InstituteForm(FlaskForm): pheno_abbrev = StringField("Abbreviation", validators=[validators.Optional()]) gene_panels = NonValidatingSelectMultipleField( - "Gene panels for variants filtering", validators=[validators.Optional()] + "Gene panels available for variants filtering", validators=[validators.Optional()] + ) + + gene_panels_matching = NonValidatingSelectMultipleField( + "Gene panels available for other variants matching (managed and causatives variants)", + validators=[validators.Optional()], ) pheno_groups = NonValidatingSelectMultipleField("Custom phenotype groups", choices=hpo_tuples) diff --git a/scout/server/blueprints/institutes/templates/overview/utils.html b/scout/server/blueprints/institutes/templates/overview/utils.html index 76a4b607e2..1d0ead192e 100644 --- a/scout/server/blueprints/institutes/templates/overview/utils.html +++ b/scout/server/blueprints/institutes/templates/overview/utils.html @@ -85,6 +85,15 @@

General Institute Settings

+
+ {{ form.institutes.label(class="control-label",data_bs_toggle="tooltip", data_bs_placement="top", title="Allow case sharing only with preselected institutes.") }} + +
+
{{ form.gene_panels.label(class="control-label",data_bs_toggle="tooltip", data_bs_placement="top", title="Select gene panels that will be available for variants filtering.") }} + {% for choice in form.gene_panels_matching.choices or [] %} + + {% endfor %} + +
+ +
+ +
+ +
{{ form.pheno_groups.label(class="control-label",data_bs_toggle="tooltip", data_bs_placement="top", title="Phenotype groups are used to quickly assign a certain phenotype to a case, on the case page.") }}
-
+
{{form.cohorts.label(class="control-label",data_bs_toggle="tooltip", data_bs_placement="top", title="Categories used to subdivide patients")}}
@@ -115,18 +137,9 @@

General Institute Settings

-
-
- {{ form.institutes.label(class="control-label",data_bs_toggle="tooltip", data_bs_placement="top", title="Allow case sharing only with preselected institutes.") }} - -
{% if "admin" in current_user.roles %}