scout-browser 4.99.0__py3-none-any.whl → 4.100.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scout/adapter/mongo/case.py +30 -15
- scout/adapter/mongo/clinvar.py +23 -31
- scout/adapter/mongo/event.py +14 -4
- scout/adapter/mongo/omics_variant.py +14 -1
- scout/adapter/mongo/query.py +24 -1
- scout/adapter/mongo/variant.py +37 -19
- scout/adapter/mongo/variant_loader.py +159 -176
- scout/build/individual.py +3 -1
- scout/commands/download/ensembl.py +1 -2
- scout/commands/load/research.py +2 -3
- scout/commands/update/individual.py +1 -0
- scout/constants/__init__.py +7 -2
- scout/constants/igv_tracks.py +4 -3
- scout/constants/indexes.py +5 -4
- scout/constants/query_terms.py +1 -0
- scout/models/case/case.py +1 -0
- scout/models/case/case_loading_models.py +3 -1
- scout/parse/ensembl.py +8 -3
- scout/server/app.py +6 -0
- scout/server/blueprints/alignviewers/templates/alignviewers/igv_viewer.html +10 -0
- scout/server/blueprints/cases/controllers.py +9 -3
- scout/server/blueprints/cases/templates/cases/case_report.html +25 -13
- scout/server/blueprints/cases/templates/cases/chanjo2_form.html +1 -1
- scout/server/blueprints/cases/templates/cases/collapsible_actionbar.html +1 -1
- scout/server/blueprints/cases/templates/cases/gene_panel.html +1 -1
- scout/server/blueprints/cases/templates/cases/utils.html +19 -0
- scout/server/blueprints/clinvar/controllers.py +5 -1
- scout/server/blueprints/clinvar/templates/clinvar/clinvar_submissions.html +34 -12
- scout/server/blueprints/clinvar/templates/clinvar/multistep_add_variant.html +1 -1
- scout/server/blueprints/diagnoses/static/diagnoses.js +8 -1
- scout/server/blueprints/institutes/static/variants_list_scripts.js +9 -1
- scout/server/blueprints/institutes/templates/overview/institute_sidebar.html +9 -1
- scout/server/blueprints/mme/__init__.py +1 -0
- scout/server/blueprints/mme/controllers.py +18 -0
- scout/server/blueprints/mme/templates/mme/mme_submissions.html +153 -0
- scout/server/blueprints/mme/views.py +34 -0
- scout/server/blueprints/panels/templates/panels/panel.html +19 -6
- scout/server/blueprints/phenotypes/templates/phenotypes/hpo_terms.html +8 -1
- scout/server/blueprints/variant/controllers.py +19 -10
- scout/server/blueprints/variant/templates/variant/acmg.html +9 -0
- scout/server/blueprints/variant/templates/variant/cancer-variant.html +1 -1
- scout/server/blueprints/variant/templates/variant/components.html +19 -16
- scout/server/blueprints/variant/templates/variant/sv-variant.html +2 -2
- scout/server/blueprints/variant/templates/variant/utils.html +20 -8
- scout/server/blueprints/variant/templates/variant/variant.html +42 -1
- scout/server/blueprints/variant/views.py +12 -0
- scout/server/blueprints/variants/controllers.py +17 -9
- scout/server/blueprints/variants/forms.py +8 -3
- scout/server/blueprints/variants/templates/variants/components.html +8 -2
- scout/server/blueprints/variants/templates/variants/indicators.html +11 -13
- scout/server/blueprints/variants/templates/variants/utils.html +27 -22
- scout/server/extensions/bionano_extension.py +0 -1
- scout/server/extensions/chanjo2_extension.py +54 -13
- scout/server/links.py +15 -0
- scout/server/static/bs_styles.css +34 -6
- scout/server/templates/utils.html +9 -10
- scout/server/utils.py +18 -0
- scout/utils/ensembl_biomart_clients.py +1 -0
- scout/utils/scout_requests.py +1 -3
- {scout_browser-4.99.0.dist-info → scout_browser-4.100.1.dist-info}/METADATA +1 -1
- {scout_browser-4.99.0.dist-info → scout_browser-4.100.1.dist-info}/RECORD +64 -60
- {scout_browser-4.99.0.dist-info → scout_browser-4.100.1.dist-info}/WHEEL +0 -0
- {scout_browser-4.99.0.dist-info → scout_browser-4.100.1.dist-info}/entry_points.txt +0 -0
- {scout_browser-4.99.0.dist-info → scout_browser-4.100.1.dist-info}/licenses/LICENSE +0 -0
scout/adapter/mongo/case.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
import datetime
|
3
|
+
import json
|
3
4
|
import logging
|
4
5
|
import operator
|
5
6
|
import re
|
@@ -831,20 +832,11 @@ class CaseHandler(object):
|
|
831
832
|
result = self.case_collection.delete_one(query)
|
832
833
|
return result
|
833
834
|
|
834
|
-
def check_existing_data(
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
case_obj(dict): case dictionary to be loaded/reuploaded
|
840
|
-
existing_case(dict): a case with same _id or same display_name and institute_id as case_obj
|
841
|
-
institute_obj(dict): institute dictionary
|
842
|
-
update(bool): If existing case should be updated
|
843
|
-
keep_actions(bool): If old evaluated variants should be kept when case is updated
|
844
|
-
|
845
|
-
Returns:
|
846
|
-
previous_evaluated_variants(list): list of variants evaluated in previous case
|
847
|
-
or None if case is not already present in the database.
|
835
|
+
def check_existing_data(
|
836
|
+
self, case_obj: dict, existing_case: dict, institute_obj: dict, update, keep_actions: bool
|
837
|
+
) -> Optional[List[dict]]:
|
838
|
+
"""Make sure data from a case to be loaded/re-uploaded conforms to any case data already saved in the database.
|
839
|
+
If keep_actions is True, return any evaluated variants to be propagated to the updated case. The flag indicates that old evaluated variants should be kept when the case is updated.
|
848
840
|
"""
|
849
841
|
|
850
842
|
if existing_case is None:
|
@@ -886,6 +878,29 @@ class CaseHandler(object):
|
|
886
878
|
# collect all variants with user actions for this case
|
887
879
|
return list(self.evaluated_variants(case_obj["_id"], institute_obj["_id"]))
|
888
880
|
|
881
|
+
def update_case_phenotypes(self, old_case: dict, new_case: dict):
|
882
|
+
"""If case has been re-run/re-uploaded, remember phenotype-related settings from the old case, including assigned diseases, HPO terms, phenotype groups and HPO panels."""
|
883
|
+
for key in [
|
884
|
+
"dynamic_panel_phenotypes",
|
885
|
+
"dynamic_gene_list",
|
886
|
+
"dynamic_gene_list_edited",
|
887
|
+
]: # Remember key/values from old case
|
888
|
+
if key in old_case:
|
889
|
+
new_case[key] = old_case[key]
|
890
|
+
|
891
|
+
for key in [
|
892
|
+
"phenotype_terms",
|
893
|
+
"phenotype_groups",
|
894
|
+
"diagnosis_phenotypes",
|
895
|
+
]: # Remember key/values from old case and integrate with info provided on case config file
|
896
|
+
if key not in old_case:
|
897
|
+
continue
|
898
|
+
new_case[key] = list(
|
899
|
+
{
|
900
|
+
json.dumps(d, sort_keys=True): d for d in new_case.get(key, []) + old_case[key]
|
901
|
+
}.values()
|
902
|
+
)
|
903
|
+
|
889
904
|
def update_case_data_sharing(self, old_case: dict, new_case: dict):
|
890
905
|
"""Update data sharing info for a case that is re-runned/re-uploaded."""
|
891
906
|
for key in ["beacon", "mme_submission"]:
|
@@ -1008,6 +1023,7 @@ class CaseHandler(object):
|
|
1008
1023
|
finally:
|
1009
1024
|
if existing_case:
|
1010
1025
|
self.update_case_data_sharing(old_case=existing_case, new_case=case_obj)
|
1026
|
+
self.update_case_phenotypes(old_case=existing_case, new_case=case_obj)
|
1011
1027
|
case_obj["rerun_requested"] = False
|
1012
1028
|
if case_obj["status"] in ["active", "archived"]:
|
1013
1029
|
case_obj["status"] = "inactive"
|
@@ -1017,7 +1033,6 @@ class CaseHandler(object):
|
|
1017
1033
|
institute_id=institute_obj["_id"],
|
1018
1034
|
force_update_case=True,
|
1019
1035
|
)
|
1020
|
-
|
1021
1036
|
self.update_case_cli(case_obj, institute_obj)
|
1022
1037
|
# update Sanger status for the new inserted variants
|
1023
1038
|
self.update_case_sanger_variants(institute_obj, case_obj, old_sanger_variants)
|
scout/adapter/mongo/clinvar.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
import logging
|
3
3
|
from datetime import datetime
|
4
|
-
from typing import Optional
|
4
|
+
from typing import List, Optional
|
5
5
|
|
6
6
|
import pymongo
|
7
7
|
from bson import ObjectId
|
@@ -218,63 +218,55 @@ class ClinVarHandler(object):
|
|
218
218
|
|
219
219
|
return sorted_case_data or case_data_list
|
220
220
|
|
221
|
-
def
|
222
|
-
"""
|
223
|
-
|
224
|
-
|
225
|
-
|
221
|
+
def _basic_submission_info(self, result: dict) -> dict:
|
222
|
+
"""Extracts the basic submission fields."""
|
223
|
+
user = self.user(user_id=result.get("created_by"))
|
224
|
+
return {
|
225
|
+
"_id": result.get("_id"),
|
226
|
+
"status": result.get("status"),
|
227
|
+
"institute_id": result.get("institute_id"),
|
228
|
+
"created_at": result.get("created_at"),
|
229
|
+
"created_by": user["name"] if user else None,
|
230
|
+
"updated_at": result.get("updated_at"),
|
231
|
+
}
|
226
232
|
|
227
|
-
|
228
|
-
|
229
|
-
"""
|
230
|
-
# get first all submission objects
|
233
|
+
def clinvar_submissions(self, institute_id: str) -> List[dict]:
|
234
|
+
"""Collect all open and closed clinvar submissions for an institute"""
|
231
235
|
query = dict(institute_id=institute_id)
|
232
236
|
results = list(
|
233
237
|
self.clinvar_submission_collection.find(query).sort("updated_at", pymongo.DESCENDING)
|
234
238
|
)
|
235
239
|
|
236
240
|
submissions = []
|
237
|
-
# Loop over all ClinVar submissions for an institute
|
238
241
|
for result in results:
|
239
|
-
submission =
|
242
|
+
submission = self._basic_submission_info(result)
|
240
243
|
cases = {}
|
241
|
-
|
242
|
-
|
243
|
-
submission["status"] = result.get("status")
|
244
|
-
submission["institute_id"] = result.get("institute_id")
|
245
|
-
submission["created_at"] = result.get("created_at")
|
246
|
-
submission["created_by"] = user["name"] if user else None
|
247
|
-
submission["updated_at"] = result.get("updated_at")
|
248
|
-
|
249
|
-
if "clinvar_subm_id" in result:
|
244
|
+
|
245
|
+
if result.get("clinvar_subm_id"):
|
250
246
|
submission["clinvar_subm_id"] = result["clinvar_subm_id"]
|
251
247
|
|
252
|
-
# If submission has variants registered
|
253
248
|
if result.get("variant_data"):
|
254
249
|
submission["variant_data"] = list(
|
255
250
|
self.clinvar_collection.find({"_id": {"$in": result["variant_data"]}}).sort(
|
256
251
|
"last_evaluated", pymongo.ASCENDING
|
257
252
|
)
|
258
253
|
)
|
259
|
-
|
260
|
-
# Loop over variants contained in a single ClinVar submission
|
261
254
|
for var_info in submission["variant_data"]:
|
262
|
-
# get case_id from variant id (caseID_variant_ID)
|
263
255
|
case_id = var_info["_id"].rsplit("_", 1)[0]
|
264
256
|
CASE_CLINVAR_SUBMISSION_PROJECTION = {"display_name": 1}
|
257
|
+
var_info["added_by"] = self.clinvar_variant_submitter(
|
258
|
+
institute_id=institute_id, case_id=case_id, variant_id=var_info["local_id"]
|
259
|
+
)
|
265
260
|
case_obj = self.case(
|
266
261
|
case_id=case_id, projection=CASE_CLINVAR_SUBMISSION_PROJECTION
|
267
262
|
)
|
263
|
+
if not case_obj:
|
264
|
+
cases[case_id] = f"{case_id} (N/A)" # Situation when case has been removed
|
265
|
+
continue
|
268
266
|
cases[case_id] = case_obj.get("display_name")
|
269
267
|
|
270
|
-
# retrieve user responsible for adding the variant to the submission
|
271
|
-
var_info["added_by"] = self.clinvar_variant_submitter(
|
272
|
-
institute_id=institute_id, case_id=case_id, variant_id=var_info["local_id"]
|
273
|
-
)
|
274
|
-
|
275
268
|
submission["cases"] = cases
|
276
269
|
|
277
|
-
# If submission has case data registered
|
278
270
|
if result.get("case_data"):
|
279
271
|
unsorted_case_data = list(
|
280
272
|
self.clinvar_collection.find({"_id": {"$in": result["case_data"]}})
|
scout/adapter/mongo/event.py
CHANGED
@@ -240,16 +240,26 @@ class EventHandler(CaseEventHandler, VariantEventHandler):
|
|
240
240
|
|
241
241
|
return self.event_collection.find(query)
|
242
242
|
|
243
|
-
def
|
243
|
+
def institute_events_by_verb(
|
244
|
+
self, category: str, institute_id: dict, verb: str
|
245
|
+
) -> pymongo.cursor.Cursor:
|
246
|
+
"""Return events with a specific verb for an institute from the newest."""
|
247
|
+
query = {
|
248
|
+
"category": category,
|
249
|
+
"institute": institute_id,
|
250
|
+
"verb": verb,
|
251
|
+
}
|
252
|
+
return self.event_collection.find(query).sort("updated_at", pymongo.DESCENDING)
|
253
|
+
|
254
|
+
def case_events_by_verb(
|
255
|
+
self, category: str, institute: dict, case: dict, verb: str
|
256
|
+
) -> pymongo.cursor.Cursor:
|
244
257
|
"""Return events with a specific verb for a case of an institute
|
245
258
|
Args:
|
246
259
|
category (str): "case" or "variant"
|
247
260
|
institute (dict): an institute id
|
248
261
|
case (dict): a case id
|
249
262
|
verb (str): an event action verb, example: "dismiss_variant"
|
250
|
-
|
251
|
-
Returns:
|
252
|
-
pymongo.Cursor: Query results
|
253
263
|
"""
|
254
264
|
query = {
|
255
265
|
"category": category,
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import logging
|
2
|
-
from typing import Dict, Optional
|
2
|
+
from typing import Dict, Iterable, List, Optional
|
3
3
|
|
4
4
|
from pymongo import ASCENDING, DESCENDING
|
5
5
|
|
@@ -217,3 +217,16 @@ class OmicsVariantHandler:
|
|
217
217
|
case_id, query=query, variant_ids=variant_ids, category=category, build=build
|
218
218
|
)
|
219
219
|
return self.omics_variant_collection.count_documents(query)
|
220
|
+
|
221
|
+
def get_omics_variants_hgnc_overlapping(
|
222
|
+
self, hgnc_ids: List[int], variant_type: str, variant_obj: dict
|
223
|
+
) -> Iterable[Dict]:
|
224
|
+
"""Return WTS outliers matching the genes of the DNA variant in question."""
|
225
|
+
query = {
|
226
|
+
"$and": [
|
227
|
+
{"case_id": variant_obj["case_id"]},
|
228
|
+
{"variant_type": variant_type},
|
229
|
+
{"hgnc_ids": {"$in": hgnc_ids}},
|
230
|
+
]
|
231
|
+
}
|
232
|
+
return self.omics_variant_collection.find(query)
|
scout/adapter/mongo/query.py
CHANGED
@@ -13,7 +13,9 @@ from scout.constants import (
|
|
13
13
|
)
|
14
14
|
|
15
15
|
CLNSIG_NOT_EXISTS = {"clnsig": {"$exists": False}}
|
16
|
+
CLNSIG_ONC_NOT_EXISTS = {"clnsig_onc": {"$exists": False}}
|
16
17
|
CLNSIG_NULL = {"clnsig": {"$eq": None}}
|
18
|
+
CLNSIG_ONC_NULL = {"clnsig_onc": {"$eq": None}}
|
17
19
|
CRITERION_EXCLUDE_OPERATOR = {False: "$in", True: "$nin"}
|
18
20
|
EXISTS = {"$exists": True}
|
19
21
|
NOT_EXISTS = {"$exists": False}
|
@@ -790,7 +792,9 @@ class QueryHandler(object):
|
|
790
792
|
|
791
793
|
if criterion == "svtype":
|
792
794
|
svtype = query["svtype"]
|
793
|
-
mongo_secondary_query.append(
|
795
|
+
mongo_secondary_query.append(
|
796
|
+
{"sub_category": {"$in": [re.compile(sub_category) for sub_category in svtype]}}
|
797
|
+
)
|
794
798
|
|
795
799
|
if criterion == "decipher":
|
796
800
|
mongo_query["decipher"] = EXISTS
|
@@ -848,6 +852,25 @@ class QueryHandler(object):
|
|
848
852
|
fusion_caller_query.append({caller: "Pass"})
|
849
853
|
mongo_secondary_query.append({"$or": fusion_caller_query})
|
850
854
|
|
855
|
+
if criterion == "clinsig_onc":
|
856
|
+
|
857
|
+
elem_match = re.compile("|".join(query.get("clinsig_onc")))
|
858
|
+
|
859
|
+
if query.get("clinsig_onc_exclude"):
|
860
|
+
mongo_secondary_query.append(
|
861
|
+
{
|
862
|
+
"$or": [
|
863
|
+
{
|
864
|
+
"clnsig_onc.value": {"$not": elem_match}
|
865
|
+
}, # Exclude values in `elem_match`
|
866
|
+
CLNSIG_ONC_NOT_EXISTS, # Field does not exist
|
867
|
+
CLNSIG_ONC_NULL, # Field is null
|
868
|
+
]
|
869
|
+
}
|
870
|
+
)
|
871
|
+
else:
|
872
|
+
mongo_secondary_query.append({"clnsig_onc.value": elem_match})
|
873
|
+
|
851
874
|
return mongo_secondary_query
|
852
875
|
|
853
876
|
|
scout/adapter/mongo/variant.py
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
# stdlib modules
|
3
3
|
import logging
|
4
4
|
import re
|
5
|
-
from typing import Any, Dict, Iterable
|
5
|
+
from typing import Any, Dict, Iterable, List, Optional, Tuple
|
6
6
|
|
7
7
|
# Third party modules
|
8
8
|
import pymongo
|
@@ -689,28 +689,16 @@ class VariantHandler(VariantLoader):
|
|
689
689
|
result = self.variant_collection.delete_many(query)
|
690
690
|
LOG.info("{0} variants deleted".format(result.deleted_count))
|
691
691
|
|
692
|
-
def
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
The operation is slightly different depending on the category of the variants that we want to collect.
|
698
|
-
If variant_obj is an SV it will return the hgnc_id matching SVs, SNVs, and MEIs but
|
699
|
-
for SNVs we will only return the SVs and MEIs since the genmod compounds are way better.
|
700
|
-
|
701
|
-
Do not return the present variant as matching.
|
702
|
-
|
703
|
-
limit: A maximum count of returned variants is introduced: mainly this is a problem when SVs are huge since there can be many genes and overlapping variants.
|
704
|
-
We sort to offer the LIMIT most severe overlapping variants.
|
705
|
-
"""
|
692
|
+
def get_variants_hgnc_overlapping(
|
693
|
+
self, hgnc_ids: List[int], variant_type: str, limit: Optional[int], variant_obj: dict
|
694
|
+
) -> Iterable[Dict]:
|
695
|
+
"""Return DNA other categories of DNA variants matching the genes of the DNA variant in question."""
|
706
696
|
category = (
|
707
697
|
{"$in": ["sv", "mei"]}
|
708
698
|
if variant_obj["category"] == "snv"
|
709
699
|
else {"$in": ["sv", "snv", "mei", "cancer", "cancer_sv"]}
|
710
700
|
)
|
711
701
|
|
712
|
-
variant_type = variant_obj.get("variant_type", "clinical")
|
713
|
-
hgnc_ids = variant_obj["hgnc_ids"]
|
714
702
|
if not limit:
|
715
703
|
limit = 30 if variant_obj["category"] == "snv" else 45
|
716
704
|
|
@@ -720,12 +708,42 @@ class VariantHandler(VariantLoader):
|
|
720
708
|
{"category": category},
|
721
709
|
{"variant_type": variant_type},
|
722
710
|
{"hgnc_ids": {"$in": hgnc_ids}},
|
723
|
-
{"variant_id": {"$ne": variant_obj["variant_id"]}},
|
724
711
|
]
|
725
712
|
}
|
726
713
|
sort_key = [("rank_score", pymongo.DESCENDING)]
|
727
714
|
|
728
|
-
return
|
715
|
+
return [
|
716
|
+
variant
|
717
|
+
for variant in self.variant_collection.find(query).sort(sort_key).limit(limit)
|
718
|
+
if variant["variant_id"] != variant_obj["variant_id"]
|
719
|
+
]
|
720
|
+
|
721
|
+
def hgnc_overlapping(
|
722
|
+
self, variant_obj: dict, limit: int = None
|
723
|
+
) -> Tuple[Iterable[Dict], Iterable[Dict]]:
|
724
|
+
"""Return overlapping variants.
|
725
|
+
|
726
|
+
Look at the genes that a variant overlaps, then return all variants that overlap these genes.
|
727
|
+
|
728
|
+
The operation is slightly different depending on the category of the variants that we want to collect.
|
729
|
+
If variant_obj is an SV it will return the hgnc_id matching SVs, SNVs, and MEIs but
|
730
|
+
for SNVs we will only return the SVs and MEIs since the genmod compounds are way better.
|
731
|
+
|
732
|
+
Do not return the present variant as matching.
|
733
|
+
|
734
|
+
limit: A maximum count of returned variants is introduced: mainly this is a problem when SVs are huge since there can be many genes and overlapping variants.
|
735
|
+
We sort to offer the LIMIT most severe overlapping variants.
|
736
|
+
"""
|
737
|
+
hgnc_ids = variant_obj.get("hgnc_ids", [])
|
738
|
+
variant_type = variant_obj.get("variant_type", "clinical")
|
739
|
+
return (
|
740
|
+
self.get_variants_hgnc_overlapping(
|
741
|
+
hgnc_ids=hgnc_ids, variant_type=variant_type, limit=limit, variant_obj=variant_obj
|
742
|
+
),
|
743
|
+
self.get_omics_variants_hgnc_overlapping(
|
744
|
+
hgnc_ids=hgnc_ids, variant_type=variant_type, variant_obj=variant_obj
|
745
|
+
),
|
746
|
+
)
|
729
747
|
|
730
748
|
def evaluated_variant_ids_from_events(self, case_id, institute_id):
|
731
749
|
"""Returns variant ids for variants that have been evaluated
|