scout-browser 4.79.1__py3-none-any.whl → 4.81__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/__version__.py +1 -1
- scout/adapter/mongo/variant.py +2 -2
- scout/constants/__init__.py +3 -0
- scout/constants/clinvar.py +3 -0
- scout/constants/indexes.py +14 -12
- scout/constants/variants_export.py +11 -0
- scout/models/case/case_loading_models.py +45 -23
- scout/models/clinvar.py +1 -0
- scout/parse/variant/variant.py +27 -23
- scout/server/blueprints/api/views.py +12 -0
- scout/server/blueprints/cases/controllers.py +72 -6
- scout/server/blueprints/cases/templates/cases/case.html +1 -22
- scout/server/blueprints/cases/templates/cases/case_bionano.html +2 -0
- scout/server/blueprints/cases/templates/cases/case_report.html +135 -129
- scout/server/blueprints/cases/templates/cases/case_sma.html +2 -0
- scout/server/blueprints/cases/templates/cases/collapsible_actionbar.html +12 -2
- scout/server/blueprints/cases/templates/cases/gene_panel.html +45 -22
- scout/server/blueprints/cases/templates/cases/utils.html +52 -14
- scout/server/blueprints/cases/views.py +22 -0
- scout/server/blueprints/clinvar/controllers.py +2 -0
- scout/server/blueprints/clinvar/form.py +5 -0
- scout/server/blueprints/clinvar/templates/clinvar/multistep_add_variant.html +30 -5
- scout/server/blueprints/diagnoses/templates/diagnoses/diagnoses.html +1 -1
- scout/server/blueprints/institutes/static/select2_darktheme.css +62 -0
- scout/server/blueprints/institutes/templates/overview/institute_settings.html +1 -1
- scout/server/blueprints/panels/templates/panels/panel.html +15 -1
- scout/server/blueprints/panels/templates/panels/panel_pdf_case_hits.html +2 -2
- scout/server/blueprints/panels/templates/panels/panel_pdf_simple.html +3 -3
- scout/server/blueprints/panels/views.py +5 -1
- scout/server/blueprints/variant/controllers.py +5 -2
- scout/server/blueprints/variant/templates/variant/variant_details.html +32 -20
- scout/server/blueprints/variants/controllers.py +179 -93
- scout/server/blueprints/variants/templates/variants/components.html +5 -4
- scout/server/blueprints/variants/templates/variants/fusion-variants.html +1 -1
- scout/server/blueprints/variants/views.py +30 -15
- scout/server/config.py +3 -0
- scout/server/templates/report_base.html +3 -3
- scout/server/templates/utils.html +68 -38
- scout/server/utils.py +25 -3
- {scout_browser-4.79.1.dist-info → scout_browser-4.81.dist-info}/METADATA +1 -1
- {scout_browser-4.79.1.dist-info → scout_browser-4.81.dist-info}/RECORD +45 -45
- scout/server/blueprints/cases/templates/cases/clinvar.html +0 -48
- {scout_browser-4.79.1.dist-info → scout_browser-4.81.dist-info}/LICENSE +0 -0
- {scout_browser-4.79.1.dist-info → scout_browser-4.81.dist-info}/WHEEL +0 -0
- {scout_browser-4.79.1.dist-info → scout_browser-4.81.dist-info}/entry_points.txt +0 -0
- {scout_browser-4.79.1.dist-info → scout_browser-4.81.dist-info}/top_level.txt +0 -0
scout/__version__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "4.
|
1
|
+
__version__ = "4.81"
|
scout/adapter/mongo/variant.py
CHANGED
@@ -671,7 +671,7 @@ class VariantHandler(VariantLoader):
|
|
671
671
|
result = self.variant_collection.delete_many(query)
|
672
672
|
LOG.info("{0} variants deleted".format(result.deleted_count))
|
673
673
|
|
674
|
-
def overlapping(self, variant_obj):
|
674
|
+
def overlapping(self, variant_obj, limit=30):
|
675
675
|
"""Return overlapping variants.
|
676
676
|
|
677
677
|
Look at the genes that a variant overlaps to.
|
@@ -701,7 +701,7 @@ class VariantHandler(VariantLoader):
|
|
701
701
|
}
|
702
702
|
sort_key = [("rank_score", pymongo.DESCENDING)]
|
703
703
|
# We collect the 30 most severe overlapping variants
|
704
|
-
variants = self.variant_collection.find(query).sort(sort_key).limit(
|
704
|
+
variants = self.variant_collection.find(query).sort(sort_key).limit(limit)
|
705
705
|
|
706
706
|
return variants
|
707
707
|
|
scout/constants/__init__.py
CHANGED
@@ -35,6 +35,7 @@ from .clinvar import (
|
|
35
35
|
COLLECTION_METHOD,
|
36
36
|
CONDITION_PREFIX,
|
37
37
|
GERMLINE_CLASSIF_TERMS,
|
38
|
+
MULTIPLE_CONDITION_EXPLANATION,
|
38
39
|
)
|
39
40
|
from .clnsig import CLINSIG_MAP, REV_CLINSIG_MAP, TRUSTED_REVSTAT_LEVEL
|
40
41
|
from .disease_parsing import (
|
@@ -99,6 +100,7 @@ from .variants_export import (
|
|
99
100
|
CANCER_EXPORT_HEADER,
|
100
101
|
EXPORT_HEADER,
|
101
102
|
EXPORTED_VARIANTS_LIMIT,
|
103
|
+
FUSION_EXPORT_HEADER,
|
102
104
|
MITODEL_HEADER,
|
103
105
|
MT_COV_STATS_HEADER,
|
104
106
|
MT_EXPORT_HEADER,
|
@@ -202,6 +204,7 @@ CALLERS = {
|
|
202
204
|
{"id": "ascat", "name": "ASCAT"},
|
203
205
|
{"id": "dellycnv", "name": "DellyCNV"},
|
204
206
|
{"id": "tiddit", "name": "TIDDIT"},
|
207
|
+
{"id": "igh_dux4", "name": "IGH-DUX4 detection"},
|
205
208
|
],
|
206
209
|
"mei": [{"id": "retroseq", "name": "RetroSeq"}],
|
207
210
|
"sv": [
|
scout/constants/clinvar.py
CHANGED
@@ -30,6 +30,7 @@ CLINVAR_HEADER = {
|
|
30
30
|
"condition_id_type": "Condition ID type",
|
31
31
|
"condition_id_value": "Condition ID value",
|
32
32
|
"condition_comment": "Condition comment",
|
33
|
+
"explanation_for_multiple_conditions": "Explanation for multiple conditions",
|
33
34
|
"clinsig": "Germline classification",
|
34
35
|
"clinsig_comment": "Comment on classification",
|
35
36
|
"last_evaluated": "Date last evaluated",
|
@@ -187,3 +188,5 @@ CONDITION_PREFIX = {
|
|
187
188
|
}
|
188
189
|
|
189
190
|
CLINVAR_ASSERTION_METHOD_CIT_DB_OPTIONS = {"DOI", "pmc", "PMID"}
|
191
|
+
|
192
|
+
MULTIPLE_CONDITION_EXPLANATION = ["Novel disease", "Uncertain", "Co-occurring"]
|
scout/constants/indexes.py
CHANGED
@@ -34,39 +34,41 @@ INDEXES = {
|
|
34
34
|
[
|
35
35
|
("case_id", ASCENDING),
|
36
36
|
("category", ASCENDING),
|
37
|
+
("variant_type", ASCENDING),
|
37
38
|
("variant_rank", ASCENDING),
|
39
|
+
("hgnc_ids", ASCENDING),
|
38
40
|
],
|
39
|
-
name="
|
41
|
+
name="caseid_category_varianttype_variantrank_hgncids",
|
40
42
|
background=True,
|
41
43
|
),
|
42
44
|
IndexModel(
|
43
45
|
[
|
44
|
-
("
|
46
|
+
("hgnc_symbols", ASCENDING),
|
47
|
+
("rank_score", DESCENDING),
|
45
48
|
("category", ASCENDING),
|
46
49
|
("variant_type", ASCENDING),
|
47
|
-
("rank_score", DESCENDING),
|
48
50
|
],
|
49
|
-
name="
|
51
|
+
name="hgncsymbol_rankscore_category_varianttype",
|
50
52
|
background=True,
|
53
|
+
partialFilterExpression={"rank_score": {"$gt": 5}, "category": "snv"},
|
51
54
|
),
|
52
55
|
IndexModel(
|
53
56
|
[
|
54
|
-
("
|
55
|
-
("
|
57
|
+
("variant_id", ASCENDING),
|
58
|
+
("case_id", ASCENDING),
|
56
59
|
("category", ASCENDING),
|
57
|
-
("variant_type", ASCENDING),
|
58
60
|
],
|
59
|
-
name="
|
61
|
+
name="variantid_caseid_category",
|
60
62
|
background=True,
|
61
|
-
partialFilterExpression={"rank_score": {"$gt": 5}, "category": "snv"},
|
62
63
|
),
|
63
64
|
IndexModel(
|
64
65
|
[
|
65
|
-
("case_id", ASCENDING),
|
66
66
|
("category", ASCENDING),
|
67
|
-
("
|
67
|
+
("case_id", ASCENDING),
|
68
|
+
("variant_type", ASCENDING),
|
69
|
+
("rank_score", ASCENDING),
|
68
70
|
],
|
69
|
-
name="
|
71
|
+
name="category_caseid_varianttype_rankscore",
|
70
72
|
background=True,
|
71
73
|
),
|
72
74
|
IndexModel(
|
@@ -15,6 +15,17 @@ EXPORT_HEADER = [
|
|
15
15
|
|
16
16
|
CANCER_EXPORT_HEADER = EXPORT_HEADER + ["VAF TUMOR", "VAF NORMAL", "COSMIC ID"]
|
17
17
|
|
18
|
+
FUSION_EXPORT_HEADER = EXPORT_HEADER + [
|
19
|
+
"Fusion genes",
|
20
|
+
"Orientation",
|
21
|
+
"Frame status",
|
22
|
+
"Observed",
|
23
|
+
"Exon",
|
24
|
+
"Junction reads",
|
25
|
+
"Split reads",
|
26
|
+
"FFPM",
|
27
|
+
]
|
28
|
+
|
18
29
|
MT_EXPORT_HEADER = [
|
19
30
|
"Position",
|
20
31
|
"Change",
|
@@ -4,6 +4,7 @@ from datetime import datetime
|
|
4
4
|
from enum import Enum
|
5
5
|
from fractions import Fraction
|
6
6
|
from glob import glob
|
7
|
+
from os.path import abspath, dirname, exists, isabs
|
7
8
|
from pathlib import Path
|
8
9
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
9
10
|
|
@@ -12,7 +13,6 @@ try:
|
|
12
13
|
except ImportError:
|
13
14
|
from typing_extensions import Literal
|
14
15
|
|
15
|
-
import path
|
16
16
|
from pydantic import BaseModel, Field, field_validator, model_validator
|
17
17
|
|
18
18
|
from scout.constants import ANALYSIS_TYPES
|
@@ -49,7 +49,11 @@ CASE_FILE_PATH_CHECKS = [
|
|
49
49
|
"peddy_ped",
|
50
50
|
"peddy_ped_check",
|
51
51
|
"peddy_sex_check",
|
52
|
+
"exe_ver",
|
53
|
+
"smn_tsv",
|
54
|
+
"reference_info",
|
52
55
|
"RNAfusion_inspector",
|
56
|
+
"RNAfusion_inspector_research",
|
53
57
|
"RNAfusion_report",
|
54
58
|
"RNAfusion_report_research",
|
55
59
|
]
|
@@ -86,18 +90,13 @@ class Sex(str, Enum):
|
|
86
90
|
unknown = "unknown"
|
87
91
|
|
88
92
|
|
89
|
-
def
|
90
|
-
"""
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
try:
|
97
|
-
path = Path(string_path) or Path(_get_demo_file_absolute_path(partial_path=string_path))
|
98
|
-
return path.is_file()
|
99
|
-
except AttributeError:
|
100
|
-
return False
|
93
|
+
def _resource_abs_path(string_path: str) -> str:
|
94
|
+
"""Return the absolute path to a resource file."""
|
95
|
+
if not exists(string_path):
|
96
|
+
raise FileExistsError(f"Path {string_path} could not be found on disk.")
|
97
|
+
if isabs(string_path):
|
98
|
+
return string_path
|
99
|
+
return abspath(string_path)
|
101
100
|
|
102
101
|
|
103
102
|
#### VCF files class ####
|
@@ -125,9 +124,8 @@ class VcfFiles(BaseModel):
|
|
125
124
|
"""Make sure that VCF file exists on disk."""
|
126
125
|
for item in VCF_FILE_PATH_CHECKS:
|
127
126
|
item_path: str = values.get(item)
|
128
|
-
if item_path
|
129
|
-
|
130
|
-
|
127
|
+
if item_path:
|
128
|
+
values[item] = _resource_abs_path(item_path)
|
131
129
|
return values
|
132
130
|
|
133
131
|
|
@@ -145,6 +143,19 @@ class ChromographImages(BaseModel):
|
|
145
143
|
upd_regions: Optional[str] = None
|
146
144
|
upd_sites: Optional[str] = None
|
147
145
|
|
146
|
+
@model_validator(mode="before")
|
147
|
+
def validate_file_path(cls, config_values: Dict) -> "SampleLoader":
|
148
|
+
"""Make sure that chromograph paths associated to samples exist on disk and are absolute paths. Chromograph paths are incomplete paths, containing the path to the directory containing a number of files plus the prefix of the files."""
|
149
|
+
|
150
|
+
for key in cls.model_fields:
|
151
|
+
item_path: str = config_values.get(key)
|
152
|
+
if item_path:
|
153
|
+
item_path_dirname: str = dirname(item_path)
|
154
|
+
config_values[key] = item_path.replace(
|
155
|
+
item_path_dirname, _resource_abs_path(item_path_dirname)
|
156
|
+
)
|
157
|
+
return config_values
|
158
|
+
|
148
159
|
|
149
160
|
class Mitodel(BaseModel):
|
150
161
|
discordant: Optional[int] = None
|
@@ -160,6 +171,15 @@ class REViewer(BaseModel):
|
|
160
171
|
reference: Optional[str] = None
|
161
172
|
reference_index: Optional[str] = None
|
162
173
|
|
174
|
+
@model_validator(mode="before")
|
175
|
+
def validate_file_path(cls, config_values: Dict) -> "SampleLoader":
|
176
|
+
"""Make sure that REViewer paths associated to samples exist on disk and are absolute paths."""
|
177
|
+
for key in cls.model_fields:
|
178
|
+
item_path: str = config_values.get(key)
|
179
|
+
if item_path:
|
180
|
+
config_values[key] = _resource_abs_path(item_path)
|
181
|
+
return config_values
|
182
|
+
|
163
183
|
|
164
184
|
class SampleLoader(BaseModel):
|
165
185
|
alignment_path: Optional[str] = None
|
@@ -246,9 +266,8 @@ class SampleLoader(BaseModel):
|
|
246
266
|
"""Make sure that files associated to samples (mostly alignment files) exist on disk."""
|
247
267
|
for item in SAMPLES_FILE_PATH_CHECKS:
|
248
268
|
item_path: str = values.get(item)
|
249
|
-
if item_path
|
250
|
-
|
251
|
-
|
269
|
+
if item_path:
|
270
|
+
values[item] = _resource_abs_path(item_path)
|
252
271
|
return values
|
253
272
|
|
254
273
|
@field_validator("capture_kits", mode="before")
|
@@ -283,8 +302,9 @@ class Image(BaseModel):
|
|
283
302
|
raise TypeError(
|
284
303
|
f"Custom images should be of type: {', '.join(SUPPORTED_IMAGE_FORMATS)}"
|
285
304
|
)
|
286
|
-
if REPID not in path and
|
305
|
+
if REPID not in path and exists(path) is False:
|
287
306
|
raise ValueError(f"Image path '{path}' is not valid.")
|
307
|
+
|
288
308
|
return path
|
289
309
|
|
290
310
|
|
@@ -317,13 +337,14 @@ def set_custom_images(images: Optional[List[Image]]) -> Optional[List[Image]]:
|
|
317
337
|
"description": image.description.replace(REPID, match["repid"]),
|
318
338
|
"height": image.height,
|
319
339
|
"format": None,
|
320
|
-
"path": str(match["path"]),
|
340
|
+
"path": _resource_abs_path(str(match["path"])),
|
321
341
|
"str_repid": match["repid"],
|
322
342
|
"title": image.title.replace(REPID, match["repid"]),
|
323
343
|
"width": image.width,
|
324
344
|
}
|
325
345
|
real_folder_images.append(Image(**new_image))
|
326
346
|
else:
|
347
|
+
image.path = _resource_abs_path(image.path)
|
327
348
|
real_folder_images.append(image) # append other non-repid images
|
328
349
|
|
329
350
|
return real_folder_images
|
@@ -387,6 +408,7 @@ class CaseLoader(BaseModel):
|
|
387
408
|
peddy_sex_check: Optional[str] = Field(None, alias="peddy_sex") # Soon to be deprecated
|
388
409
|
phenotype_groups: Optional[List[str]] = None
|
389
410
|
phenotype_terms: Optional[List[str]] = None
|
411
|
+
exe_ver: Optional[str] = None
|
390
412
|
rank_model_version: Optional[str] = None
|
391
413
|
rank_score_threshold: Optional[int] = 0
|
392
414
|
reference_info: Optional[str] = None
|
@@ -502,8 +524,8 @@ class CaseLoader(BaseModel):
|
|
502
524
|
"""Make sure the files associated to the case (mostly reports) exist on disk."""
|
503
525
|
for item in CASE_FILE_PATH_CHECKS:
|
504
526
|
item_path: str = values.get(item)
|
505
|
-
if item_path
|
506
|
-
|
527
|
+
if item_path:
|
528
|
+
values[item] = _resource_abs_path(item_path)
|
507
529
|
|
508
530
|
return values
|
509
531
|
|
scout/models/clinvar.py
CHANGED
scout/parse/variant/variant.py
CHANGED
@@ -271,7 +271,6 @@ def get_genmod_key(case):
|
|
271
271
|
"""
|
272
272
|
case_id = case["_id"]
|
273
273
|
if "-" in case_id:
|
274
|
-
LOG.debug("iCase ID contains '-'. Using display_name as case _id.")
|
275
274
|
return case["display_name"]
|
276
275
|
return case["_id"]
|
277
276
|
|
@@ -474,39 +473,44 @@ def add_gene_and_transcript_info_for_fusions(
|
|
474
473
|
Return:
|
475
474
|
parsed_transcripts(list)
|
476
475
|
"""
|
476
|
+
|
477
477
|
parsed_transcripts = []
|
478
478
|
genes = []
|
479
|
+
hgnc_ids = []
|
480
|
+
hgnc_symbols = []
|
481
|
+
|
479
482
|
for suffix in ["a", "b"]:
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
+
# If fusions have transcript information about a fusion partner
|
484
|
+
parsed_transcript = {}
|
485
|
+
if parsed_variant.get(f"transcript_id_{suffix}"):
|
486
|
+
# Add transcript info to genes if available
|
487
|
+
parsed_transcript = {
|
488
|
+
"transcript_id": parsed_variant[f"transcript_id_{suffix}"],
|
483
489
|
"hgnc_id": parsed_variant[f"hgnc_id_{suffix}"],
|
484
|
-
"
|
490
|
+
"hgnc_symbol": parsed_variant[f"gene_{suffix}"],
|
491
|
+
"exon": parsed_variant[f"exon_number_{suffix}"],
|
485
492
|
}
|
486
|
-
|
487
|
-
|
488
|
-
if parsed_variant
|
489
|
-
|
493
|
+
parsed_transcripts.append(parsed_transcript)
|
494
|
+
|
495
|
+
if parsed_variant.get(f"hgnc_id_{suffix}"):
|
496
|
+
if parsed_variant.get(f"gene_{suffix}"):
|
497
|
+
# Add hgnc_symbol to variant if available
|
498
|
+
hgnc_symbols.append(parsed_variant.get(f"gene_{suffix}"))
|
499
|
+
|
500
|
+
# Add hgnc_id to variant if available
|
501
|
+
hgnc_ids.append(parsed_variant.get(f"hgnc_id_{suffix}"))
|
502
|
+
|
503
|
+
genes.append(
|
490
504
|
{
|
491
|
-
"transcript_id": parsed_variant[f"transcript_id_{suffix}"],
|
492
|
-
"hgnc_id": parsed_variant[f"hgnc_id_{suffix}"],
|
493
505
|
"hgnc_symbol": parsed_variant[f"gene_{suffix}"],
|
494
|
-
"
|
506
|
+
"hgnc_id": parsed_variant[f"hgnc_id_{suffix}"],
|
507
|
+
"transcripts": [parsed_transcript] if parsed_transcript else [],
|
495
508
|
}
|
496
509
|
)
|
497
510
|
|
498
|
-
|
499
|
-
if parsed_transcripts:
|
500
|
-
genes[0]["transcripts"] = [parsed_transcripts[0]]
|
501
|
-
genes[1]["transcripts"] = [parsed_transcripts[1]]
|
502
|
-
|
503
|
-
# Add hgnc_id to variant if available
|
504
|
-
hgnc_ids = []
|
505
|
-
if parsed_variant["hgnc_id_a"] and parsed_variant["hgnc_id_b"]:
|
506
|
-
hgnc_ids = [gene["hgnc_id"] for gene in genes]
|
507
|
-
parsed_variant["genes"] = genes
|
508
|
-
|
511
|
+
parsed_variant["genes"] = genes
|
509
512
|
parsed_variant["hgnc_ids"] = hgnc_ids
|
513
|
+
parsed_variant["hgnc_symbols"] = hgnc_symbols
|
510
514
|
|
511
515
|
return parsed_transcripts
|
512
516
|
|
@@ -67,6 +67,12 @@ def pin_variant(
|
|
67
67
|
|
68
68
|
(institute_obj, case_obj, variant_obj) = _lookup_variant(variant_id, institute_id, case_name)
|
69
69
|
|
70
|
+
if not institute_id:
|
71
|
+
institute_id = variant_obj["institute"]
|
72
|
+
|
73
|
+
if not case_name:
|
74
|
+
case_name = case_obj.get("display_name")
|
75
|
+
|
70
76
|
user_obj = store.user(current_user.email)
|
71
77
|
link = url_for(
|
72
78
|
"variant.variant",
|
@@ -88,6 +94,12 @@ def unpin_variant(
|
|
88
94
|
|
89
95
|
(institute_obj, case_obj, variant_obj) = _lookup_variant(variant_id, institute_id, case_name)
|
90
96
|
|
97
|
+
if not institute_id:
|
98
|
+
institute_id = variant_obj["institute"]
|
99
|
+
|
100
|
+
if not case_name:
|
101
|
+
case_name = case_obj.get("display_name")
|
102
|
+
|
91
103
|
user_obj = store.user(current_user.email)
|
92
104
|
link = url_for(
|
93
105
|
"variant.variant",
|
@@ -4,7 +4,7 @@ import itertools
|
|
4
4
|
import json
|
5
5
|
import logging
|
6
6
|
import os
|
7
|
-
from typing import Dict, List, Set
|
7
|
+
from typing import Dict, List, Optional, Set
|
8
8
|
|
9
9
|
import query_phenomizer
|
10
10
|
import requests
|
@@ -45,6 +45,8 @@ from scout.server.extensions import RerunnerError, bionano_access, gens, matchma
|
|
45
45
|
from scout.server.links import disease_link
|
46
46
|
from scout.server.utils import (
|
47
47
|
case_has_alignments,
|
48
|
+
case_has_chanjo2_coverage,
|
49
|
+
case_has_chanjo_coverage,
|
48
50
|
case_has_mt_alignments,
|
49
51
|
case_has_rna_tracks,
|
50
52
|
institute_and_case,
|
@@ -83,6 +85,61 @@ def phenomizer_diseases(hpo_ids, case_obj, p_value_treshold=1):
|
|
83
85
|
flash("Could not establish a conection to Phenomizer", "danger")
|
84
86
|
|
85
87
|
|
88
|
+
def chanjo2_coverage_report_contents(
|
89
|
+
institute_obj: dict, case_obj: dict, panel_name: str, panel_id: Optional[str], report_type: str
|
90
|
+
) -> Optional[str]:
|
91
|
+
"""Retrieve the HTML contents of the Chanjo2 coverage report/overview for a case."""
|
92
|
+
|
93
|
+
if panel_id:
|
94
|
+
hgnc_gene_ids: List[int] = store.panel_to_genes(panel_id=panel_id, gene_format="hgnc_id")
|
95
|
+
elif panel_name == "HPO Panel":
|
96
|
+
hgnc_gene_ids: List[int] = [
|
97
|
+
gene["hgnc_id"] for gene in case_obj.get("dynamic_gene_list", [])
|
98
|
+
]
|
99
|
+
else:
|
100
|
+
hgnc_gene_ids: List[int] = _get_default_panel_genes(store, case_obj)
|
101
|
+
|
102
|
+
if not hgnc_gene_ids:
|
103
|
+
flash("Case should have at least one default gene panel containing genes", "warning")
|
104
|
+
return
|
105
|
+
|
106
|
+
query_samples: List[dict] = []
|
107
|
+
analysis_types: List[str] = []
|
108
|
+
|
109
|
+
for ind in case_obj.get("individuals", []):
|
110
|
+
if not ind.get("d4_file"):
|
111
|
+
continue
|
112
|
+
query_samples.append(
|
113
|
+
{
|
114
|
+
"name": ind.get("display_name"),
|
115
|
+
"coverage_file_path": ind.get("d4_file"),
|
116
|
+
"case_name": case_obj["display_name"],
|
117
|
+
"analysis_date": case_obj["analysis_date"].isoformat(),
|
118
|
+
}
|
119
|
+
)
|
120
|
+
analysis_types.append(ind.get("analysis_type"))
|
121
|
+
|
122
|
+
interval_type = "genes"
|
123
|
+
if "wes" in analysis_types:
|
124
|
+
interval_type = "transcripts"
|
125
|
+
elif "wts" in analysis_types:
|
126
|
+
interval_type = "exons"
|
127
|
+
|
128
|
+
report_query: dict = {
|
129
|
+
"build": "GRCh38" if "38" in case_obj.get("genome_build", "37") else "GRCh37",
|
130
|
+
"default_level": institute_obj.get("coverage_cutoff"),
|
131
|
+
"interval_type": interval_type,
|
132
|
+
"panel_name": panel_name,
|
133
|
+
"case_display_name": case_obj["display_name"],
|
134
|
+
"hgnc_gene_ids": hgnc_gene_ids,
|
135
|
+
"samples": query_samples,
|
136
|
+
}
|
137
|
+
|
138
|
+
report_url: str = "/".join([current_app.config.get("CHANJO2_URL"), report_type])
|
139
|
+
response = requests.post(report_url, json=report_query)
|
140
|
+
return response.text
|
141
|
+
|
142
|
+
|
86
143
|
def coverage_report_contents(base_url, institute_obj, case_obj):
|
87
144
|
"""Capture the contents of a case coverage report (chanjo-report), to be displayed in the general case report
|
88
145
|
|
@@ -298,9 +355,11 @@ def case(store, institute_obj, case_obj):
|
|
298
355
|
store.user(user_id=user_id) for user_id in case_obj.get("assignees", [])
|
299
356
|
]
|
300
357
|
|
301
|
-
# Provide basic info on alignment files availability for this case
|
358
|
+
# Provide basic info on alignment files & coverage data availability for this case
|
302
359
|
case_has_alignments(case_obj)
|
303
360
|
case_has_mt_alignments(case_obj)
|
361
|
+
case_has_chanjo_coverage(case_obj)
|
362
|
+
case_has_chanjo2_coverage(case_obj)
|
304
363
|
|
305
364
|
case_groups = {}
|
306
365
|
case_group_label = {}
|
@@ -324,10 +383,18 @@ def case(store, institute_obj, case_obj):
|
|
324
383
|
partial_causatives = _get_partial_causatives(store, case_obj)
|
325
384
|
_populate_assessments(partial_causatives)
|
326
385
|
|
327
|
-
case_obj["
|
386
|
+
case_obj["clinvar_variants"] = store.case_to_clinVars(case_obj["_id"])
|
328
387
|
|
329
|
-
#
|
330
|
-
|
388
|
+
# check for variants submitted to clinVar but not present in suspects for the case
|
389
|
+
clinvar_variants_not_in_suspects = [
|
390
|
+
store.variant(variant_id) or variant_id
|
391
|
+
for variant_id in case_obj["clinvar_variants"]
|
392
|
+
if variant_id not in [entry.get("_id") for entry in suspects]
|
393
|
+
]
|
394
|
+
|
395
|
+
case_obj["clinvar_variants_not_in_suspects"] = clinvar_variants_not_in_suspects
|
396
|
+
|
397
|
+
case_obj["default_genes"] = _get_default_panel_genes(store, case_obj)
|
331
398
|
|
332
399
|
for hpo_term in itertools.chain(
|
333
400
|
case_obj.get("phenotype_groups") or [], case_obj.get("phenotype_terms") or []
|
@@ -355,7 +422,6 @@ def case(store, institute_obj, case_obj):
|
|
355
422
|
if institute_obj.get("collaborators")
|
356
423
|
and collab["_id"] in institute_obj.get("collaborators")
|
357
424
|
]
|
358
|
-
case_obj["clinvar_variants"] = store.case_to_clinVars(case_obj["_id"])
|
359
425
|
|
360
426
|
# if updated_at is a list, set it to the last update datetime
|
361
427
|
if case_obj.get("updated_at") and isinstance(case_obj["updated_at"], list):
|
@@ -5,7 +5,6 @@
|
|
5
5
|
{% from "cases/individuals_table.html" import cancer_individuals_table, individuals_table %}
|
6
6
|
{% from "cases/phenotype.html" import hpo_item, cohort_panel, diagnosis_phenotypes, phenotype_groups_panel, add_phenotype_terms_panel, hpo_panel %}
|
7
7
|
{% from "cases/gene_panel.html" import genepanels_table, hpo_genelist_panel %}
|
8
|
-
{% from "cases/clinvar.html" import clinvar_vars %}
|
9
8
|
|
10
9
|
{% block title %}
|
11
10
|
{{ super() }} - {{ institute.display_name }} - {{ case.display_name }}
|
@@ -148,9 +147,6 @@
|
|
148
147
|
<div class="row">
|
149
148
|
<div class="col">{{ causatives_list(causatives, partial_causatives, evaluated_variants, institute, case, manual_rank_options, cancer_tier_options) }}</div>
|
150
149
|
<div class="col">{{ suspects_list(suspects, institute, case, manual_rank_options, cancer_tier_options) }}</div>
|
151
|
-
{% if case.clinvar_variants or case.suspects %}
|
152
|
-
<div class="col">{{ clinvar_vars(institute, case, suspects) }}</div>
|
153
|
-
{% endif %}
|
154
150
|
<!-- end of data sharing panels -->
|
155
151
|
</div>
|
156
152
|
|
@@ -516,24 +512,6 @@
|
|
516
512
|
</form>
|
517
513
|
{% endmacro %}
|
518
514
|
|
519
|
-
{% macro remove_form(url, hidden_input=None, button_name=None, button_value=None) %}
|
520
|
-
<form action="{{ url }}" method="POST">
|
521
|
-
{% if hidden_input %}
|
522
|
-
<input type="hidden"
|
523
|
-
name="{{ hidden_input[0] }}"
|
524
|
-
value="{{ hidden_input[1] }}">
|
525
|
-
{% endif %}
|
526
|
-
<div class="float-end">
|
527
|
-
<button class="btn btn-link btn-sm"
|
528
|
-
name="{{ button_name if button_name }}"
|
529
|
-
value="{{ button_value if button_value }}"
|
530
|
-
type="submit">
|
531
|
-
<em class="fa fa-remove"></em>
|
532
|
-
</button>
|
533
|
-
</div>
|
534
|
-
</form>
|
535
|
-
{% endmacro %}
|
536
|
-
|
537
515
|
{% macro solve_modal(institute, case, case_tag_options) %}
|
538
516
|
<div class="modal fade" id="solve_modal" tabindex="-1" role="dialog" aria-labelledby="solveCaseLabel" aria-hidden="true">
|
539
517
|
<div class="modal-dialog" role="document">
|
@@ -623,6 +601,7 @@
|
|
623
601
|
paging: false,
|
624
602
|
searching: false,
|
625
603
|
ordering: true,
|
604
|
+
order:[[1, 'desc'],[0, 'asc']],
|
626
605
|
info: false
|
627
606
|
});
|
628
607
|
{% endif %}
|