scout-browser 4.95.0__py3-none-any.whl → 4.97.0__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.
Files changed (99) hide show
  1. scout/adapter/mongo/case.py +75 -70
  2. scout/adapter/mongo/filter.py +28 -11
  3. scout/adapter/mongo/institute.py +2 -0
  4. scout/adapter/mongo/omics_variant.py +20 -5
  5. scout/adapter/mongo/query.py +104 -95
  6. scout/adapter/mongo/variant.py +0 -5
  7. scout/adapter/mongo/variant_loader.py +10 -12
  8. scout/build/case.py +3 -1
  9. scout/build/individual.py +3 -11
  10. scout/commands/delete/delete_command.py +87 -49
  11. scout/commands/load/research.py +4 -4
  12. scout/commands/load/variants.py +25 -8
  13. scout/commands/setup/setup_scout.py +1 -1
  14. scout/commands/update/case.py +12 -0
  15. scout/commands/update/individual.py +1 -2
  16. scout/constants/__init__.py +7 -2
  17. scout/constants/acmg.py +25 -18
  18. scout/constants/file_types.py +68 -119
  19. scout/constants/filters.py +2 -1
  20. scout/constants/gene_tags.py +3 -3
  21. scout/constants/igv_tracks.py +7 -11
  22. scout/constants/query_terms.py +2 -2
  23. scout/demo/643594.config.yaml +6 -0
  24. scout/demo/643594.peddy.ped +1 -1
  25. scout/demo/643594.somalier.ancestry.tsv +4 -0
  26. scout/demo/643594.somalier.pairs.tsv +4 -0
  27. scout/demo/643594.somalier.samples.tsv +4 -0
  28. scout/demo/cancer.load_config.yaml +2 -3
  29. scout/demo/resources/__init__.py +1 -1
  30. scout/demo/resources/gnomad.v4.1.constraint_metrics_reduced.tsv +3755 -0
  31. scout/demo/rnafusion.load_config.yaml +1 -0
  32. scout/exceptions/database.py +1 -1
  33. scout/load/all.py +8 -16
  34. scout/models/case/case.py +1 -0
  35. scout/models/case/case_loading_models.py +15 -5
  36. scout/models/managed_variant.py +3 -3
  37. scout/models/omics_variant.py +3 -3
  38. scout/parse/case.py +113 -5
  39. scout/parse/pedqc.py +127 -0
  40. scout/parse/variant/frequency.py +9 -6
  41. scout/parse/variant/variant.py +71 -39
  42. scout/server/app.py +14 -0
  43. scout/server/blueprints/alignviewers/controllers.py +2 -0
  44. scout/server/blueprints/alignviewers/templates/alignviewers/igv_viewer.html +3 -0
  45. scout/server/blueprints/alignviewers/templates/alignviewers/utils.html +1 -1
  46. scout/server/blueprints/cases/controllers.py +25 -3
  47. scout/server/blueprints/cases/templates/cases/case.html +3 -0
  48. scout/server/blueprints/cases/templates/cases/case_report.html +28 -2
  49. scout/server/blueprints/cases/templates/cases/chanjo2_form.html +2 -2
  50. scout/server/blueprints/cases/templates/cases/collapsible_actionbar.html +12 -0
  51. scout/server/blueprints/cases/templates/cases/gene_panel.html +9 -3
  52. scout/server/blueprints/cases/templates/cases/individuals_table.html +4 -1
  53. scout/server/blueprints/cases/templates/cases/utils.html +23 -19
  54. scout/server/blueprints/cases/views.py +5 -9
  55. scout/server/blueprints/clinvar/controllers.py +12 -11
  56. scout/server/blueprints/clinvar/templates/clinvar/clinvar_submissions.html +10 -14
  57. scout/server/blueprints/clinvar/templates/clinvar/multistep_add_variant.html +15 -7
  58. scout/server/blueprints/clinvar/views.py +18 -31
  59. scout/server/blueprints/institutes/controllers.py +20 -1
  60. scout/server/blueprints/institutes/forms.py +5 -1
  61. scout/server/blueprints/institutes/templates/overview/institute_settings.html +7 -0
  62. scout/server/blueprints/institutes/templates/overview/utils.html +20 -1
  63. scout/server/blueprints/omics_variants/templates/omics_variants/outliers.html +9 -2
  64. scout/server/blueprints/omics_variants/views.py +8 -10
  65. scout/server/blueprints/variant/controllers.py +30 -1
  66. scout/server/blueprints/variant/templates/variant/cancer-variant.html +21 -5
  67. scout/server/blueprints/variant/templates/variant/components.html +26 -9
  68. scout/server/blueprints/variant/templates/variant/variant.html +4 -2
  69. scout/server/blueprints/variant/templates/variant/variant_details.html +1 -1
  70. scout/server/blueprints/variant/utils.py +2 -0
  71. scout/server/blueprints/variant/views.py +10 -3
  72. scout/server/blueprints/variants/controllers.py +29 -3
  73. scout/server/blueprints/variants/forms.py +37 -10
  74. scout/server/blueprints/variants/templates/variants/cancer-variants.html +5 -4
  75. scout/server/blueprints/variants/templates/variants/components.html +12 -10
  76. scout/server/blueprints/variants/templates/variants/str-variants.html +13 -9
  77. scout/server/blueprints/variants/templates/variants/utils.html +59 -36
  78. scout/server/blueprints/variants/views.py +45 -60
  79. scout/server/extensions/beacon_extension.py +1 -1
  80. scout/server/extensions/bionano_extension.py +5 -5
  81. scout/server/extensions/chanjo2_extension.py +40 -1
  82. scout/server/extensions/chanjo_extension.py +1 -1
  83. scout/server/extensions/clinvar_extension.py +56 -2
  84. scout/server/extensions/matchmaker_extension.py +1 -1
  85. scout/server/links.py +0 -14
  86. scout/server/static/bs_styles.css +2 -0
  87. scout/server/templates/layout.html +1 -0
  88. scout/server/utils.py +5 -0
  89. scout/utils/acmg.py +5 -5
  90. scout/utils/ensembl_biomart_clients.py +2 -11
  91. scout/utils/scout_requests.py +1 -1
  92. {scout_browser-4.95.0.dist-info → scout_browser-4.97.0.dist-info}/METADATA +1 -1
  93. {scout_browser-4.95.0.dist-info → scout_browser-4.97.0.dist-info}/RECORD +96 -94
  94. scout/demo/resources/gnomad.v4.0.constraint_metrics_reduced.tsv +0 -3755
  95. scout/parse/peddy.py +0 -149
  96. scout/utils/sort.py +0 -21
  97. {scout_browser-4.95.0.dist-info → scout_browser-4.97.0.dist-info}/WHEEL +0 -0
  98. {scout_browser-4.95.0.dist-info → scout_browser-4.97.0.dist-info}/entry_points.txt +0 -0
  99. {scout_browser-4.95.0.dist-info → scout_browser-4.97.0.dist-info}/licenses/LICENSE +0 -0
@@ -3,6 +3,7 @@ import datetime
3
3
  import logging
4
4
  import operator
5
5
  import re
6
+ from collections import OrderedDict
6
7
  from copy import deepcopy
7
8
  from typing import Any, Dict, List, Optional
8
9
 
@@ -11,11 +12,16 @@ from bson import ObjectId
11
12
  from werkzeug.datastructures import ImmutableMultiDict
12
13
 
13
14
  from scout.build.case import build_case
14
- from scout.constants import ACMG_MAP, CCV_MAP, FILE_TYPE_MAP, ID_PROJECTION, OMICS_FILE_TYPE_MAP
15
+ from scout.constants import (
16
+ ACMG_MAP,
17
+ CCV_MAP,
18
+ ID_PROJECTION,
19
+ ORDERED_FILE_TYPE_MAP,
20
+ ORDERED_OMICS_FILE_TYPE_MAP,
21
+ )
15
22
  from scout.exceptions import ConfigError, IntegrityError
16
23
  from scout.parse.variant.ids import parse_document_id
17
24
  from scout.utils.algorithms import ui_score
18
- from scout.utils.sort import get_load_priority
19
25
 
20
26
  LOG = logging.getLogger(__name__)
21
27
  EXISTS = "$exists"
@@ -886,11 +892,17 @@ class CaseHandler(object):
886
892
  if key in old_case:
887
893
  new_case[key] = old_case[key]
888
894
 
889
- def _load_omics_variants(self, case_obj: dict, build: str, update: bool = False):
895
+ def _load_clinical_omics_variants(self, case_obj: dict, build: str, update: bool = False):
890
896
  """Load omics variants. The OMICS FILE type dict contains all we need to
891
897
  determine how to load variants (type, category etc)."""
892
898
 
893
- for omics_file in OMICS_FILE_TYPE_MAP.keys():
899
+ CLINICAL_ORDERED_OMICS_FILE_TYPE_MAP = OrderedDict(
900
+ (key, value)
901
+ for key, value in ORDERED_OMICS_FILE_TYPE_MAP.items()
902
+ if value["variant_type"] != "research"
903
+ )
904
+
905
+ for omics_file in CLINICAL_ORDERED_OMICS_FILE_TYPE_MAP.keys():
894
906
  if not case_obj["omics_files"].get(omics_file):
895
907
  LOG.debug("didn't find %s for case, skipping", omics_file)
896
908
  continue
@@ -901,6 +913,38 @@ class CaseHandler(object):
901
913
 
902
914
  self.load_omics_variants(case_obj=case_obj, build=build, file_type=omics_file)
903
915
 
916
+ def _load_clinical_variants(self, case_obj: dict, build: str, update: bool = False):
917
+ """Load variants in the order specified by CLINICAL_ORDERED_FILE_TYPE_MAP."""
918
+ CLINICAL_ORDERED_FILE_TYPE_MAP = OrderedDict(
919
+ (key, value)
920
+ for key, value in ORDERED_FILE_TYPE_MAP.items()
921
+ if value["variant_type"] != "research"
922
+ )
923
+ load_type_cat = set()
924
+ for file_name, vcf_dict in CLINICAL_ORDERED_FILE_TYPE_MAP.items():
925
+ if not case_obj["vcf_files"].get(file_name):
926
+ LOG.debug("didn't find {}, skipping".format(file_name))
927
+ continue
928
+ load_type_cat.add((vcf_dict["variant_type"], vcf_dict["category"]))
929
+
930
+ for variant_type, category in load_type_cat:
931
+ if update:
932
+ self.delete_variants(
933
+ case_id=case_obj["_id"],
934
+ variant_type=variant_type,
935
+ category=category,
936
+ )
937
+ self.load_variants(
938
+ case_obj=case_obj,
939
+ variant_type=variant_type,
940
+ category=category,
941
+ build=build,
942
+ rank_threshold=case_obj.get("rank_score_threshold", 5),
943
+ custom_images=self._get_variants_custom_images(
944
+ variant_category=category, case=case_obj
945
+ ),
946
+ )
947
+
904
948
  def load_case(self, config_data: dict, update: bool = False, keep_actions: bool = True) -> dict:
905
949
  """Load a case into the database
906
950
 
@@ -950,77 +994,36 @@ class CaseHandler(object):
950
994
  old_evaluated_variants = list(
951
995
  self.evaluated_variants(case_obj["_id"], case_obj["owner"])
952
996
  )
953
-
954
- # load from files
955
- files = [
956
- {
957
- "file_name": file_type,
958
- "variant_type": FILE_TYPE_MAP[file_type]["variant_type"],
959
- "category": FILE_TYPE_MAP[file_type]["category"],
960
- }
961
- for file_type in FILE_TYPE_MAP.keys()
962
- if FILE_TYPE_MAP[file_type]["variant_type"] != "research"
963
- ]
964
-
965
- # (type, category) tuples are not unique - eg SNV, SNV_MT
966
- load_variants = set()
967
997
  try:
968
- for vcf_file in files:
969
- # Check if any file of this kind is configured for case
970
- if not case_obj["vcf_files"].get(vcf_file["file_name"]):
971
- LOG.debug("didn't find {}, skipping".format(vcf_file["file_name"]))
972
- continue
973
- load_variants.add((vcf_file["variant_type"], vcf_file["category"]))
974
-
975
- for variant_type, category in sorted(
976
- load_variants,
977
- key=lambda tup: get_load_priority(variant_type=tup[0], category=tup[1]),
978
- ):
979
- if update:
980
- self.delete_variants(
981
- case_id=case_obj["_id"],
982
- variant_type=variant_type,
983
- category=category,
984
- )
985
- # add variants
986
- self.load_variants(
987
- case_obj=case_obj,
988
- variant_type=variant_type,
989
- category=category,
990
- build=genome_build,
991
- rank_threshold=case_obj.get("rank_score_threshold", 5),
992
- custom_images=self._get_variants_custom_images(
993
- variant_category=category, case=case_obj
994
- ),
995
- )
998
+ self._load_clinical_variants(case_obj, build=genome_build, update=update)
999
+ self._load_clinical_omics_variants(case_obj, build=genome_build, update=update)
996
1000
 
997
1001
  except (IntegrityError, ValueError, ConfigError, KeyError) as error:
998
- LOG.warning(error)
999
-
1000
- self._load_omics_variants(case_obj, build=genome_build, update=update)
1001
-
1002
- if existing_case:
1003
- self.update_case_data_sharing(old_case=existing_case, new_case=case_obj)
1004
- case_obj["rerun_requested"] = False
1005
- if case_obj["status"] in ["active", "archived"]:
1006
- case_obj["status"] = "inactive"
1007
-
1008
- case_obj["variants_stats"] = self.case_variants_count(
1009
- case_id=case_obj["_id"],
1010
- institute_id=institute_obj["_id"],
1011
- force_update_case=True,
1012
- )
1013
-
1014
- self.update_case_cli(case_obj, institute_obj)
1015
- # update Sanger status for the new inserted variants
1016
- self.update_case_sanger_variants(institute_obj, case_obj, old_sanger_variants)
1002
+ LOG.exception(error)
1003
+ raise error
1004
+ else:
1005
+ if not existing_case:
1006
+ LOG.info("Loading case %s into database", case_obj["display_name"])
1007
+ self.add_case(case_obj, institute_obj)
1008
+ finally:
1009
+ if existing_case:
1010
+ self.update_case_data_sharing(old_case=existing_case, new_case=case_obj)
1011
+ case_obj["rerun_requested"] = False
1012
+ if case_obj["status"] in ["active", "archived"]:
1013
+ case_obj["status"] = "inactive"
1014
+
1015
+ case_obj["variants_stats"] = self.case_variants_count(
1016
+ case_id=case_obj["_id"],
1017
+ institute_id=institute_obj["_id"],
1018
+ force_update_case=True,
1019
+ )
1017
1020
 
1018
- if keep_actions and old_evaluated_variants:
1019
- self.update_variant_actions(institute_obj, case_obj, old_evaluated_variants)
1021
+ self.update_case_cli(case_obj, institute_obj)
1022
+ # update Sanger status for the new inserted variants
1023
+ self.update_case_sanger_variants(institute_obj, case_obj, old_sanger_variants)
1020
1024
 
1021
- else:
1022
- LOG.info("Loading case %s into database", case_obj["display_name"])
1023
- self.add_case(case_obj, institute_obj)
1025
+ if keep_actions and old_evaluated_variants:
1026
+ self.update_variant_actions(institute_obj, case_obj, old_evaluated_variants)
1024
1027
 
1025
1028
  return case_obj
1026
1029
 
@@ -1087,6 +1090,7 @@ class CaseHandler(object):
1087
1090
  Returns:
1088
1091
  updated_case(dict): The updated case information
1089
1092
  """
1093
+
1090
1094
  LOG.info("Updating case {0}".format(case_obj["_id"]))
1091
1095
  old_case = self.case_collection.find_one({"_id": case_obj["_id"]})
1092
1096
 
@@ -1146,6 +1150,7 @@ class CaseHandler(object):
1146
1150
  "RNAfusion_report": case_obj.get("RNAfusion_report"),
1147
1151
  "RNAfusion_report_research": case_obj.get("RNAfusion_report_research"),
1148
1152
  "rna_delivery_report": case_obj.get("rna_delivery_report"),
1153
+ "scout_load_version": case_obj.get("scout_load_version"),
1149
1154
  "smn_tsv": case_obj.get("smn_tsv"),
1150
1155
  "status": case_obj.get("status"),
1151
1156
  "sv_rank_model_version": case_obj.get("sv_rank_model_version"),
@@ -24,10 +24,10 @@ class FilterHandler(object):
24
24
  Returns:
25
25
  filter_obj(dict)
26
26
  """
27
- filter_obj = None
28
- LOG.debug("Retrieve filter {}".format(filter_id))
29
27
  filter_obj = self.filter_collection.find_one({"_id": ObjectId(filter_id)})
28
+
30
29
  if filter_obj is not None:
30
+ self.set_legacy_options(filter_obj)
31
31
  # use _id to preselect the currently loaded filter, and drop it while we are at it
32
32
  filter_obj.update([("filters", filter_obj.pop("_id", None))])
33
33
  return filter_obj
@@ -49,15 +49,6 @@ class FilterHandler(object):
49
49
  Returns:
50
50
  filter_id(str) - a unique id that can be cast to ObjectId
51
51
  """
52
-
53
- LOG.info(
54
- "Stashing filter for user '%s' and institute %s.",
55
- user_obj.get("email"),
56
- institute_obj.get("display_name"),
57
- )
58
-
59
- LOG.info("Filter object {}".format(filter_obj))
60
-
61
52
  institute_id = institute_obj.get("_id")
62
53
  filter_dict = {"institute_id": institute_id, "category": category}
63
54
 
@@ -271,3 +262,29 @@ class FilterHandler(object):
271
262
  )
272
263
 
273
264
  return filters_res
265
+
266
+ def set_legacy_options(self, filter_obj):
267
+ """Update remaining legacy filter options,
268
+ i.e. filter controls that changed names or functionality.
269
+ In particular, clinsig_confident_always_returned was split into two different
270
+ options: clinvar_trusted_revstat and prioritise_clinvar.
271
+ """
272
+ if "clinsig_confident_always_returned" not in filter_obj:
273
+ return
274
+
275
+ filter_value = filter_obj.pop("clinsig_confident_always_returned", ["True"])
276
+ filter_obj["clinvar_trusted_revstat"] = filter_value
277
+ filter_obj["prioritise_clinvar"] = filter_value
278
+
279
+ self.filter_collection.find_one_and_update(
280
+ {"_id": filter_obj["_id"]},
281
+ {
282
+ "$set": {
283
+ "clinvar_trusted_revstat": filter_value,
284
+ "prioritise_clinvar": filter_value,
285
+ },
286
+ "$unset": {
287
+ "clinsig_confident_always_returned": "",
288
+ },
289
+ },
290
+ )
@@ -56,6 +56,7 @@ class InstituteHandler(object):
56
56
  check_show_all_vars: Optional[str] = None,
57
57
  clinvar_key: Optional[str] = None,
58
58
  clinvar_submitters: Optional[List[str]] = None,
59
+ soft_filters: Optional[dict] = None,
59
60
  ) -> Union[dict, str]:
60
61
  """Update the information for an institute."""
61
62
 
@@ -127,6 +128,7 @@ class InstituteHandler(object):
127
128
  "alamut_institution": alamut_institution,
128
129
  "clinvar_key": clinvar_key,
129
130
  "show_all_cases_status": show_all_cases_status,
131
+ "soft_filters": soft_filters,
130
132
  }
131
133
  for key, value in ADMIN_SETTINGS.items():
132
134
  if value not in [None, "", []]:
@@ -1,11 +1,14 @@
1
1
  import logging
2
2
  from typing import Dict, Optional
3
3
 
4
- from scout.constants import OMICS_FILE_TYPE_MAP
4
+ from pymongo import ASCENDING, DESCENDING
5
+
6
+ from scout.constants import ORDERED_OMICS_FILE_TYPE_MAP
5
7
  from scout.models.omics_variant import OmicsVariantLoader
6
8
  from scout.parse.omics_variant import parse_omics_file
7
9
 
8
10
  LOG = logging.getLogger(__name__)
11
+ SORT_ORDER = {"asc": ASCENDING, "desc": DESCENDING}
9
12
 
10
13
 
11
14
  class OmicsVariantHandler:
@@ -30,7 +33,7 @@ class OmicsVariantHandler:
30
33
 
31
34
  def delete_omics_variants(self, case_id: str, file_type: str):
32
35
  """Delete OMICS variants for a case"""
33
- omics_file_type = OMICS_FILE_TYPE_MAP.get(file_type)
36
+ omics_file_type = ORDERED_OMICS_FILE_TYPE_MAP.get(file_type)
34
37
  category = omics_file_type["category"]
35
38
  sub_category = omics_file_type["sub_category"]
36
39
  variant_type = omics_file_type["variant_type"]
@@ -123,7 +126,7 @@ class OmicsVariantHandler:
123
126
  case_panels = case_obj.get("panels", [])
124
127
  gene_to_panels = self.gene_to_panels(case_obj)
125
128
 
126
- omics_file_type: dict = OMICS_FILE_TYPE_MAP.get(file_type)
129
+ omics_file_type: dict = ORDERED_OMICS_FILE_TYPE_MAP.get(file_type)
127
130
 
128
131
  nr_inserted = 0
129
132
 
@@ -180,9 +183,21 @@ class OmicsVariantHandler:
180
183
  else:
181
184
  nr_of_variants = skip + nr_of_variants
182
185
 
183
- query = self.build_query(case_id, query=query, category=category, build=build)
186
+ variants_query = self.build_query(case_id, query=query, category=category, build=build)
187
+
188
+ if query.get("sort_by") and query.get("sort_order"):
189
+ return (
190
+ self.omics_variant_collection.find(variants_query, projection)
191
+ .sort([(query.get("sort_by"), SORT_ORDER[query.get("sort_order")])])
192
+ .skip(skip)
193
+ .limit(nr_of_variants)
194
+ )
195
+
184
196
  return self.omics_variant_collection.find(
185
- query, projection, skip=skip, limit=nr_of_variants
197
+ variants_query,
198
+ projection,
199
+ skip=skip,
200
+ limit=nr_of_variants,
186
201
  )
187
202
 
188
203
  def count_omics_variants(