scout-browser 4.92__py3-none-any.whl → 4.94__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/base.py +3 -0
- scout/adapter/mongo/case.py +27 -2
- scout/adapter/mongo/ccv.py +131 -0
- scout/adapter/mongo/query.py +88 -53
- scout/adapter/mongo/variant.py +6 -5
- scout/adapter/mongo/variant_events.py +45 -1
- scout/build/ccv.py +59 -0
- scout/commands/export/export_command.py +0 -0
- scout/commands/load/base.py +0 -0
- scout/commands/load/user.py +0 -0
- scout/commands/serve.py +2 -1
- scout/commands/update/disease.py +0 -0
- scout/commands/update/genes.py +0 -0
- scout/commands/wipe_database.py +0 -0
- scout/constants/__init__.py +2 -0
- scout/constants/case_tags.py +2 -0
- scout/constants/ccv.py +244 -0
- scout/demo/643594.config.yaml +2 -2
- scout/demo/images/custom_images/1300x1000.jpg +0 -0
- scout/models/ccv_evaluation.py +26 -0
- scout/models/variant/variant.py +1 -0
- scout/parse/variant/compound.py +0 -0
- scout/parse/variant/gene.py +0 -0
- scout/parse/variant/genotype.py +0 -0
- scout/resources/custom_igv_tracks/mane.bb +0 -0
- scout/server/blueprints/cases/controllers.py +21 -0
- scout/server/blueprints/cases/templates/cases/case_report.html +53 -0
- scout/server/blueprints/cases/templates/cases/collapsible_actionbar.html +2 -2
- scout/server/blueprints/cases/templates/cases/index.html +0 -2
- scout/server/blueprints/clinvar/controllers.py +4 -5
- scout/server/blueprints/institutes/controllers.py +129 -67
- scout/server/blueprints/institutes/forms.py +5 -2
- scout/server/blueprints/institutes/templates/overview/cases.html +6 -0
- scout/server/blueprints/institutes/templates/overview/causatives.html +1 -1
- scout/server/blueprints/institutes/templates/overview/utils.html +18 -6
- scout/server/blueprints/institutes/templates/overview/verified.html +1 -1
- scout/server/blueprints/institutes/views.py +4 -0
- scout/server/blueprints/panels/controllers.py +5 -6
- scout/server/blueprints/panels/templates/panels/panel.html +5 -5
- scout/server/blueprints/variant/controllers.py +148 -1
- scout/server/blueprints/variant/templates/variant/cancer-variant.html +1 -1
- scout/server/blueprints/variant/templates/variant/ccv.html +183 -0
- scout/server/blueprints/variant/templates/variant/components.html +61 -3
- scout/server/blueprints/variant/templates/variant/tx_overview.html +3 -3
- scout/server/blueprints/variant/templates/variant/variant.html +1 -1
- scout/server/blueprints/variant/templates/variant/variant_details.html +29 -11
- scout/server/blueprints/variant/utils.py +21 -1
- scout/server/blueprints/variant/views.py +114 -3
- scout/server/blueprints/variants/controllers.py +31 -0
- scout/server/blueprints/variants/templates/variants/cancer-sv-variants.html +4 -18
- scout/server/blueprints/variants/templates/variants/cancer-variants.html +4 -13
- scout/server/blueprints/variants/templates/variants/components.html +77 -73
- scout/server/blueprints/variants/templates/variants/indicators.html +11 -0
- scout/server/blueprints/variants/templates/variants/sv-variants.html +2 -2
- scout/server/links.py +1 -1
- scout/server/static/custom_images.js +19 -2
- scout/utils/acmg.py +0 -1
- scout/utils/ccv.py +201 -0
- scout/utils/md5.py +0 -0
- {scout_browser-4.92.dist-info → scout_browser-4.94.dist-info}/METADATA +67 -45
- {scout_browser-4.92.dist-info → scout_browser-4.94.dist-info}/RECORD +54 -49
- {scout_browser-4.92.dist-info → scout_browser-4.94.dist-info}/WHEEL +1 -2
- scout/__version__.py +0 -1
- scout/demo/images/custom_images/640x480_two.jpg +0 -0
- scout_browser-4.92.dist-info/top_level.txt +0 -1
- {scout_browser-4.92.dist-info → scout_browser-4.94.dist-info}/entry_points.txt +0 -0
- {scout_browser-4.92.dist-info → scout_browser-4.94.dist-info/licenses}/LICENSE +0 -0
@@ -3,7 +3,7 @@ import datetime
|
|
3
3
|
import logging
|
4
4
|
from typing import Dict, List, Optional, Tuple
|
5
5
|
|
6
|
-
from flask import Response, current_app, flash, url_for
|
6
|
+
from flask import Response, current_app, flash, request, url_for
|
7
7
|
from flask_login import current_user
|
8
8
|
from pymongo import ASCENDING, DESCENDING
|
9
9
|
from pymongo.cursor import Cursor
|
@@ -11,10 +11,13 @@ from werkzeug.datastructures import Headers, MultiDict
|
|
11
11
|
|
12
12
|
from scout.adapter.mongo.base import MongoAdapter
|
13
13
|
from scout.constants import (
|
14
|
+
CANCER_PHENOTYPE_MAP,
|
14
15
|
CASE_STATUSES,
|
15
16
|
DATE_DAY_FORMATTER,
|
16
17
|
ID_PROJECTION,
|
17
18
|
PHENOTYPE_GROUPS,
|
19
|
+
PHENOTYPE_MAP,
|
20
|
+
SEX_MAP,
|
18
21
|
VARIANTS_TARGET_FROM_CATEGORY,
|
19
22
|
)
|
20
23
|
from scout.server.blueprints.variant.utils import predictions, update_representative_gene
|
@@ -422,97 +425,129 @@ def _sort_cases(data, request, all_cases):
|
|
422
425
|
return all_cases
|
423
426
|
|
424
427
|
|
425
|
-
def
|
426
|
-
"""
|
428
|
+
def export_case_samples(institute_id, filtered_cases) -> Response:
|
429
|
+
"""Export to CSV file a list of samples from selected cases."""
|
430
|
+
EXPORT_HEADER = [
|
431
|
+
"Sample ID",
|
432
|
+
"Sample Name",
|
433
|
+
"Analysis",
|
434
|
+
"Affected status",
|
435
|
+
"Sex",
|
436
|
+
"Sex confirmed",
|
437
|
+
"Parenthood confirmed",
|
438
|
+
"Predicted ancestry",
|
439
|
+
"Tissue",
|
440
|
+
"Case Name",
|
441
|
+
"Case ID",
|
442
|
+
"Analysis date",
|
443
|
+
"Case Status",
|
444
|
+
"Case phenotypes",
|
445
|
+
"Research",
|
446
|
+
"Track",
|
447
|
+
"Default panels",
|
448
|
+
"Genome build",
|
449
|
+
"SNV/SV rank models",
|
450
|
+
]
|
451
|
+
export_lines = []
|
452
|
+
export_lines.append("\t".join(EXPORT_HEADER)) # Use tab-separated values
|
453
|
+
for case in filtered_cases:
|
454
|
+
for individual in case.get("individuals", []):
|
455
|
+
export_line = [
|
456
|
+
individual["individual_id"],
|
457
|
+
individual["display_name"],
|
458
|
+
individual.get("analysis_type").upper(),
|
459
|
+
(
|
460
|
+
CANCER_PHENOTYPE_MAP[individual.get("phenotype", 0)]
|
461
|
+
if case.get("track") == "cancer"
|
462
|
+
else PHENOTYPE_MAP[individual.get("phenotype", 0)]
|
463
|
+
),
|
464
|
+
SEX_MAP[individual.get("sex", 0)],
|
465
|
+
individual.get("confirmed_sex") or "-",
|
466
|
+
individual.get("confirmed_parent") or "-",
|
467
|
+
individual.get("predicted_ancestry") or "-",
|
468
|
+
individual.get("tissue_type") or "-",
|
469
|
+
case["display_name"],
|
470
|
+
case["_id"],
|
471
|
+
case["analysis_date"].strftime("%Y-%m-%d %H:%M:%S"),
|
472
|
+
case["status"],
|
473
|
+
", ".join(hpo["phenotype_id"] for hpo in case.get("phenotype_terms", [])),
|
474
|
+
case.get("is_research"),
|
475
|
+
case.get("track"),
|
476
|
+
", ".join(
|
477
|
+
panel["panel_name"] for panel in case.get("panels") if panel.get("is_default")
|
478
|
+
),
|
479
|
+
case.get("genome_build"),
|
480
|
+
f"{case.get('rank_model_version', '-')}/{case.get('sv_rank_model_version', '-')}",
|
481
|
+
]
|
482
|
+
export_lines.append("\t".join(str(item) for item in export_line))
|
427
483
|
|
428
|
-
|
484
|
+
file_content = "\n".join(export_lines)
|
429
485
|
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
486
|
+
return Response(
|
487
|
+
file_content,
|
488
|
+
mimetype="text/plain",
|
489
|
+
headers={
|
490
|
+
"Content-Disposition": f"attachment;filename={institute_id}_cases_{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}.txt"
|
491
|
+
},
|
492
|
+
)
|
434
493
|
|
435
|
-
|
436
|
-
|
437
|
-
"""
|
494
|
+
|
495
|
+
def cases(store: MongoAdapter, request: request, institute_id: str) -> dict:
|
496
|
+
"""Preprocess case objects for the 'cases' view."""
|
438
497
|
data = {}
|
498
|
+
|
499
|
+
# Initialize data (institute info, filters, and case counts)
|
439
500
|
institute_obj = institute_and_case(store, institute_id)
|
440
501
|
data["institute"] = institute_obj
|
441
|
-
|
442
|
-
name_query = request.form
|
443
|
-
limit = int(request.form.get("search_limit")) if request.form.get("search_limit") else 100
|
444
|
-
|
445
502
|
data["form"] = CaseFilterForm(request.form)
|
503
|
+
data["status_ncases"] = store.nr_cases_by_status(institute_id=institute_id)
|
504
|
+
data["nr_cases"] = sum(data["status_ncases"].values())
|
505
|
+
|
506
|
+
# Fetch Sanger unevaluated and validated cases
|
507
|
+
sanger_ordered_not_validated = get_sanger_unevaluated(store, institute_id, current_user.email)
|
508
|
+
data["sanger_unevaluated"], data["sanger_validated_by_others"] = sanger_ordered_not_validated
|
446
509
|
|
510
|
+
# Projection for fetching cases
|
447
511
|
ALL_CASES_PROJECTION = {
|
448
512
|
"analysis_date": 1,
|
449
513
|
"assignees": 1,
|
450
514
|
"beacon": 1,
|
451
515
|
"case_id": 1,
|
452
516
|
"display_name": 1,
|
517
|
+
"genome_build": 1,
|
453
518
|
"individuals": 1,
|
454
519
|
"is_rerun": 1,
|
455
520
|
"is_research": 1,
|
456
521
|
"mme_submission": 1,
|
457
522
|
"owner": 1,
|
458
523
|
"panels": 1,
|
524
|
+
"phenotype_terms": 1,
|
525
|
+
"rank_model_version": 1,
|
459
526
|
"status": 1,
|
527
|
+
"sv_rank_model_version": 1,
|
460
528
|
"track": 1,
|
461
529
|
"vcf_files": 1,
|
462
530
|
}
|
463
531
|
|
464
|
-
|
465
|
-
data["nr_cases"] = sum(data["status_ncases"].values())
|
466
|
-
|
467
|
-
sanger_ordered_not_validated: Tuple[Dict[str, list]] = get_sanger_unevaluated(
|
468
|
-
store, institute_id, current_user.email
|
469
|
-
)
|
470
|
-
data["sanger_unevaluated"]: Dict[str, list] = sanger_ordered_not_validated[0]
|
471
|
-
data["sanger_validated_by_others"]: Dict[str, list] = sanger_ordered_not_validated[1]
|
472
|
-
|
473
|
-
# local function to add info to case obj
|
474
|
-
def populate_case_obj(case_obj):
|
475
|
-
analysis_types = set(ind["analysis_type"] for ind in case_obj["individuals"])
|
476
|
-
if len(analysis_types) > 1:
|
477
|
-
LOG.debug("Set analysis types to {'mixed'}")
|
478
|
-
analysis_types = set(["mixed"])
|
479
|
-
|
480
|
-
case_obj["analysis_types"] = list(analysis_types)
|
481
|
-
case_obj["assignees"] = [
|
482
|
-
store.user(user_email) for user_email in case_obj.get("assignees", [])
|
483
|
-
]
|
484
|
-
# Check if case was re-runned
|
485
|
-
last_analysis_date = case_obj.get("analysis_date", datetime.datetime.now())
|
486
|
-
all_analyses_dates = set()
|
487
|
-
for analysis in case_obj.get("analyses", [{"date": last_analysis_date}]):
|
488
|
-
all_analyses_dates.add(analysis.get("date", last_analysis_date))
|
489
|
-
|
490
|
-
case_obj["is_rerun"] = len(all_analyses_dates) > 1 or last_analysis_date > max(
|
491
|
-
all_analyses_dates
|
492
|
-
)
|
493
|
-
|
494
|
-
case_obj["clinvar_variants"] = store.case_to_clinVars(case_obj["_id"])
|
495
|
-
case_obj["display_track"] = TRACKS[case_obj.get("track", "rare")]
|
496
|
-
return case_obj
|
497
|
-
|
532
|
+
# Group cases by status
|
498
533
|
case_groups = {status: [] for status in CASE_STATUSES}
|
534
|
+
nr_cases_showall_statuses = 0
|
535
|
+
status_show_all_cases = institute_obj.get("show_all_cases_status") or ["prioritized"]
|
499
536
|
|
500
|
-
|
501
|
-
0 # Nr of cases for the case statuses where all cases should be shown
|
502
|
-
)
|
503
|
-
# In institute settings, retrieve all case status categories for which all cases should be displayed
|
504
|
-
status_show_all_cases: List[str] = institute_obj.get("show_all_cases_status") or ["prioritized"]
|
537
|
+
# Process cases for statuses that require all cases to be shown
|
505
538
|
for status in status_show_all_cases:
|
506
539
|
cases_in_status = store.cases_by_status(
|
507
540
|
institute_id=institute_id, status=status, projection=ALL_CASES_PROJECTION
|
508
541
|
)
|
509
542
|
cases_in_status = _sort_cases(data, request, cases_in_status)
|
510
543
|
for case_obj in cases_in_status:
|
511
|
-
populate_case_obj(case_obj)
|
512
|
-
nr_cases_showall_statuses += 1
|
544
|
+
populate_case_obj(case_obj, store)
|
513
545
|
case_groups[status].append(case_obj)
|
546
|
+
nr_cases_showall_statuses += 1
|
514
547
|
|
515
|
-
#
|
548
|
+
# Fetch additional cases based on filters
|
549
|
+
name_query = request.form
|
550
|
+
limit = int(request.form.get("search_limit")) if request.form.get("search_limit") else 100
|
516
551
|
all_cases = store.cases(
|
517
552
|
collaborator=institute_id,
|
518
553
|
name_query=name_query,
|
@@ -525,17 +560,20 @@ def cases(store, request, institute_id):
|
|
525
560
|
)
|
526
561
|
all_cases = _sort_cases(data, request, all_cases)
|
527
562
|
|
563
|
+
if request.form.get("export"):
|
564
|
+
return export_case_samples(institute_id, all_cases)
|
565
|
+
|
566
|
+
# Process additional cases for the remaining statuses
|
528
567
|
nr_cases = 0
|
529
568
|
for case_obj in all_cases:
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
569
|
+
if case_obj["status"] not in status_show_all_cases:
|
570
|
+
if nr_cases == limit:
|
571
|
+
break
|
572
|
+
populate_case_obj(case_obj, store)
|
573
|
+
case_groups[case_obj["status"]].append(case_obj)
|
574
|
+
nr_cases += 1
|
575
|
+
|
576
|
+
# Compile the final data
|
539
577
|
data["cases"] = [(status, case_groups[status]) for status in CASE_STATUSES]
|
540
578
|
data["found_cases"] = nr_cases + nr_cases_showall_statuses
|
541
579
|
data["limit"] = limit
|
@@ -543,8 +581,32 @@ def cases(store, request, institute_id):
|
|
543
581
|
return data
|
544
582
|
|
545
583
|
|
584
|
+
def populate_case_obj(case_obj: dict, store: MongoAdapter):
|
585
|
+
"""Helper function to populate additional case information."""
|
586
|
+
analysis_types = set(ind["analysis_type"] for ind in case_obj["individuals"])
|
587
|
+
if len(analysis_types) > 1:
|
588
|
+
analysis_types = set(["mixed"])
|
589
|
+
case_obj["analysis_types"] = list(analysis_types)
|
590
|
+
|
591
|
+
case_obj["assignees"] = [store.user(user_email) for user_email in case_obj.get("assignees", [])]
|
592
|
+
|
593
|
+
last_analysis_date = case_obj.get("analysis_date", datetime.datetime.now())
|
594
|
+
all_analyses_dates = {
|
595
|
+
analysis.get("date", last_analysis_date)
|
596
|
+
for analysis in case_obj.get("analyses", [{"date": last_analysis_date}])
|
597
|
+
}
|
598
|
+
case_obj["is_rerun"] = len(all_analyses_dates) > 1 or last_analysis_date > max(
|
599
|
+
all_analyses_dates
|
600
|
+
)
|
601
|
+
|
602
|
+
case_obj["clinvar_variants"] = store.case_to_clinVars(case_obj["_id"])
|
603
|
+
case_obj["display_track"] = TRACKS.get(case_obj.get("track", "rare"))
|
604
|
+
|
605
|
+
|
546
606
|
def _get_unevaluated_variants_for_case(
|
547
|
-
case_obj: dict,
|
607
|
+
case_obj: dict,
|
608
|
+
var_ids_list: List[str],
|
609
|
+
sanger_validated_by_user_by_case: Dict[str, List[str]],
|
548
610
|
) -> Tuple[Dict[str, list]]:
|
549
611
|
"""Returns the variants with Sanger ordered by a user that need validation or are validated by another user."""
|
550
612
|
|
@@ -73,7 +73,8 @@ class InstituteForm(FlaskForm):
|
|
73
73
|
pheno_abbrev = StringField("Abbreviation", validators=[validators.Optional()])
|
74
74
|
|
75
75
|
gene_panels = NonValidatingSelectMultipleField(
|
76
|
-
"Gene panels available for variants filtering",
|
76
|
+
"Gene panels available for variants filtering",
|
77
|
+
validators=[validators.Optional()],
|
77
78
|
)
|
78
79
|
|
79
80
|
gene_panels_matching = NonValidatingSelectMultipleField(
|
@@ -144,7 +145,8 @@ class GeneVariantFiltersForm(FlaskForm):
|
|
144
145
|
variant_type = SelectMultipleField(choices=[("clinical", "clinical"), ("research", "research")])
|
145
146
|
category = SelectMultipleField(choices=CATEGORY_CHOICES)
|
146
147
|
hgnc_symbols = TagListField(
|
147
|
-
"HGNC Symbols (comma-separated, case sensitive)",
|
148
|
+
"HGNC Symbols (comma-separated, case sensitive)",
|
149
|
+
validators=[validators.InputRequired()],
|
148
150
|
)
|
149
151
|
rank_score = IntegerField(default=15)
|
150
152
|
phenotype_terms = TagListField("HPO terms (comma-separated)")
|
@@ -181,3 +183,4 @@ class CaseFilterForm(FlaskForm):
|
|
181
183
|
has_rna = BooleanField("Has RNA-seq data")
|
182
184
|
validation_ordered = BooleanField("Validation pending")
|
183
185
|
search = SubmitField(label="Search")
|
186
|
+
export = SubmitField(label="Filter and export")
|
@@ -249,6 +249,12 @@
|
|
249
249
|
advSearchBlock.hide();
|
250
250
|
}
|
251
251
|
|
252
|
+
function StopSpinner() {
|
253
|
+
// Avoid page spinner being stuck on file download
|
254
|
+
$(window).unbind('beforeunload');
|
255
|
+
return true;
|
256
|
+
}
|
257
|
+
|
252
258
|
advBlockCheckbox.on('click', function() {
|
253
259
|
if($(this).is(':checked')) {
|
254
260
|
advSearchBlock.show();
|
@@ -29,7 +29,7 @@
|
|
29
29
|
<div class="container-float">
|
30
30
|
<div class="row" id="body-row"> <!--sidebar and main container are on the same row-->
|
31
31
|
<div class="col-12">
|
32
|
-
{{ variant_list_content(institute, causatives, acmg_map, callers, inherit_palette) }}
|
32
|
+
{{ variant_list_content(institute, causatives, acmg_map, ccv_map, callers, inherit_palette) }}
|
33
33
|
</div>
|
34
34
|
</div> <!-- end of div id body-row -->
|
35
35
|
</div>
|
@@ -9,7 +9,7 @@
|
|
9
9
|
{% endif %}
|
10
10
|
{% endfor %}
|
11
11
|
|
12
|
-
<form action="{{ form_action }}" method="POST" accept-charset="utf-8">
|
12
|
+
<form action="{{ form_action }}" method="POST" accept-charset="utf-8" onsubmit="return StopSpinner()">
|
13
13
|
{{ form.hidden_tag() }}
|
14
14
|
|
15
15
|
<div class="row">
|
@@ -19,7 +19,7 @@
|
|
19
19
|
{{ form.search_institute(class="form-control") }}
|
20
20
|
</div>
|
21
21
|
{% endif %}
|
22
|
-
<div class="col-md-
|
22
|
+
<div class="col-md-2 mb-3">
|
23
23
|
{{ form.case.label(class="form-label") }}
|
24
24
|
{{ form.case(class="form-control", placeholder="example:18201", pattern="[^\\\<\>\?\!\=\/]*", title="Characters \<>?!=/ are not allowed") }}
|
25
25
|
</div>
|
@@ -27,9 +27,10 @@
|
|
27
27
|
{{ form.search_limit.label(class="form-label") }}
|
28
28
|
{{ form.search_limit(class="form-control") }}
|
29
29
|
</div>
|
30
|
-
<div class="btn-sm mb-2 col-md-
|
31
|
-
{{ form.search(class="btn btn-
|
32
|
-
|
30
|
+
<div class="btn-sm mb-2 col-md-3 mx-auto">
|
31
|
+
{{ form.search(class="btn btn-primary mt-4") }}
|
32
|
+
{{ form.export(class="btn btn-primary mt-4") }}
|
33
|
+
<a href="{{ reset_action }}" class="btn btn-secondary mt-4 text-white">Reset search</a>
|
33
34
|
</div>
|
34
35
|
<div class="col-md-2 mb-3 form-check align-self-center mt-3 ms-3" data-bs-toggle="tooltip" title="Using multiple search criteria will narrow down your results as returned cases will contain all your searched conditions.">
|
35
36
|
{{ form.advanced_search(class="form-check-input") }}
|
@@ -446,7 +447,7 @@
|
|
446
447
|
{% endmacro %}
|
447
448
|
|
448
449
|
|
449
|
-
{% macro variant_list_content(institute, variants, acmg_map, callers, inherit_palette) %}
|
450
|
+
{% macro variant_list_content(institute, variants, acmg_map, ccv_map, callers, inherit_palette) %}
|
450
451
|
<div class="card mt-3">
|
451
452
|
<div class="card-body overflow-auto">
|
452
453
|
<table id="variants_table" class="table display table-sm">
|
@@ -469,6 +470,7 @@
|
|
469
470
|
<th data-bs-toggle='tooltip' data-bs-container='body' title="ref/alt-GQ">Zygosity</th>
|
470
471
|
<th>Inheritance</th>
|
471
472
|
<th>ACMG</th>
|
473
|
+
<th>ClinGen-CGC-VICC</th>
|
472
474
|
<th>Case</th>
|
473
475
|
<th>Analysis type</th>
|
474
476
|
<th>Validated status</th>
|
@@ -608,6 +610,16 @@
|
|
608
610
|
{% endif %}
|
609
611
|
</a>
|
610
612
|
</td>
|
613
|
+
<td><!-- Clingen-CGC-VIGG -->
|
614
|
+
<a href="#" data-bs-toggle="tooltip" title="Clingen-CGC-VIGG classification assigned by Scout users"
|
615
|
+
style="text-decoration: none; color: #000;">
|
616
|
+
{% if 'ccv_classification' in variant %}
|
617
|
+
<span class="badge bg-{{ccv_map[variant.ccv_classification].color}}">{{ccv_map[variant.ccv_classification].short}}</span>
|
618
|
+
{% else %}
|
619
|
+
-
|
620
|
+
{% endif %}
|
621
|
+
</a>
|
622
|
+
</td>
|
611
623
|
<td><!-- Case -->
|
612
624
|
<a href="{{ url_for('cases.case',
|
613
625
|
institute_id=institute._id,
|
@@ -58,7 +58,7 @@
|
|
58
58
|
{{validated_chart()}}
|
59
59
|
</div>
|
60
60
|
<div class="row">
|
61
|
-
<div class="col-12">{{ variant_list_content(institute, verified, acmg_map, callers, inherit_palette) }}</div>
|
61
|
+
<div class="col-12">{{ variant_list_content(institute, verified, acmg_map, ccv_map, callers, inherit_palette) }}</div>
|
62
62
|
</div>
|
63
63
|
</div>
|
64
64
|
</div>
|
@@ -10,6 +10,8 @@ from scout.constants import (
|
|
10
10
|
ACMG_COMPLETE_MAP,
|
11
11
|
ACMG_MAP,
|
12
12
|
CALLERS,
|
13
|
+
CCV_COMPLETE_MAP,
|
14
|
+
CCV_MAP,
|
13
15
|
INHERITANCE_PALETTE,
|
14
16
|
VERBS_ICONS_MAP,
|
15
17
|
VERBS_MAP,
|
@@ -72,6 +74,7 @@ def verified(institute_id):
|
|
72
74
|
return dict(
|
73
75
|
acmg_map={key: ACMG_COMPLETE_MAP[value] for key, value in ACMG_MAP.items()},
|
74
76
|
callers=CALLERS,
|
77
|
+
ccv_map={key: CCV_COMPLETE_MAP[value] for key, value in CCV_MAP.items()},
|
75
78
|
inherit_palette=INHERITANCE_PALETTE,
|
76
79
|
institute=institute_obj,
|
77
80
|
verified=verified_vars,
|
@@ -87,6 +90,7 @@ def causatives(institute_id):
|
|
87
90
|
acmg_map={key: ACMG_COMPLETE_MAP[value] for key, value in ACMG_MAP.items()},
|
88
91
|
callers=CALLERS,
|
89
92
|
causatives=controllers.causatives(institute_obj, request),
|
93
|
+
ccv_map={key: CCV_COMPLETE_MAP[value] for key, value in CCV_MAP.items()},
|
90
94
|
institute=institute_obj,
|
91
95
|
inherit_palette=INHERITANCE_PALETTE,
|
92
96
|
)
|
@@ -160,7 +160,6 @@ def update_panel(store, panel_name, csv_lines, option):
|
|
160
160
|
Returns:
|
161
161
|
panel_obj(dict)
|
162
162
|
"""
|
163
|
-
new_genes = []
|
164
163
|
panel_obj = store.gene_panel(panel_name)
|
165
164
|
if panel_obj is None:
|
166
165
|
return None
|
@@ -202,11 +201,11 @@ def update_panel(store, panel_name, csv_lines, option):
|
|
202
201
|
)
|
203
202
|
|
204
203
|
info_data = {
|
205
|
-
"disease_associated_transcripts": new_gene
|
206
|
-
"reduced_penetrance": new_gene
|
207
|
-
"mosaicism": new_gene
|
208
|
-
"inheritance_models": new_gene
|
209
|
-
"database_entry_version": new_gene
|
204
|
+
"disease_associated_transcripts": new_gene.get("transcripts", []),
|
205
|
+
"reduced_penetrance": new_gene.get("reduced_penetrance", ""),
|
206
|
+
"mosaicism": new_gene.get("mosaicism", ""),
|
207
|
+
"inheritance_models": new_gene.get("inheritance_models", []),
|
208
|
+
"database_entry_version": new_gene.get("database_entry_version", ""),
|
210
209
|
}
|
211
210
|
if (
|
212
211
|
option == "replace"
|
@@ -223,15 +223,15 @@
|
|
223
223
|
<span data-bs-toggle="tooltip" data-bs-placement="right" data-bs-html="true" title="
|
224
224
|
<div class='text-left'>
|
225
225
|
<strong>Transcripts</strong>:
|
226
|
-
{{ gene.info.disease_associated_transcripts|join(',') }} <br>
|
226
|
+
{{ gene.info.disease_associated_transcripts|join(',') if gene.info.disease_associated_transcripts }} <br>
|
227
227
|
<strong>Reduced penetrance</strong>:
|
228
|
-
{{
|
228
|
+
{{ gene.info.reduced_penetrance if gene.info.reduced_penetrance }} <br>
|
229
229
|
<strong>Mosaicism</strong>:
|
230
|
-
{{
|
230
|
+
{{ gene.info.mosaicism if gene.info.mosaicism }} <br>
|
231
231
|
<strong>Inheritance models</strong>:
|
232
|
-
{{ gene.info.inheritance_models|join(',') }} <br>
|
232
|
+
{{ gene.info.inheritance_models|join(',') if gene.info.inheritance_models }} <br>
|
233
233
|
<strong>Entry version</strong>:
|
234
|
-
{{ gene.info.database_entry_version }} <br>
|
234
|
+
{{ gene.info.database_entry_version if gene.info.database_entry_version }} <br>
|
235
235
|
</div>
|
236
236
|
">
|
237
237
|
{{ gene.symbol }}
|
@@ -16,6 +16,10 @@ from scout.constants import (
|
|
16
16
|
CANCER_SPECIFIC_VARIANT_DISMISS_OPTIONS,
|
17
17
|
CANCER_TIER_OPTIONS,
|
18
18
|
CASE_TAGS,
|
19
|
+
CCV_COMPLETE_MAP,
|
20
|
+
CCV_CRITERIA,
|
21
|
+
CCV_MAP,
|
22
|
+
CCV_OPTIONS,
|
19
23
|
DISMISS_VARIANT_OPTIONS,
|
20
24
|
IGV_TRACKS,
|
21
25
|
INHERITANCE_PALETTE,
|
@@ -41,6 +45,7 @@ from scout.server.utils import (
|
|
41
45
|
from .utils import (
|
42
46
|
add_gene_info,
|
43
47
|
associate_variant_genes_with_case_panels,
|
48
|
+
ccv_evaluation,
|
44
49
|
clinsig_human,
|
45
50
|
default_panels,
|
46
51
|
end_position,
|
@@ -194,9 +199,11 @@ def variant(
|
|
194
199
|
'cancer_tier_options': CANCER_TIER_OPTIONS,
|
195
200
|
'dismiss_variant_options': DISMISS_VARIANT_OPTIONS,
|
196
201
|
'ACMG_OPTIONS': ACMG_OPTIONS,
|
202
|
+
'CCV_OPTIONS': CCV_OPTIONS,
|
197
203
|
'igv_tracks': IGV_TRACKS,
|
198
204
|
'gens_info': <dict>,
|
199
205
|
'evaluations': <list(evaluations)>,
|
206
|
+
'ccv_evaluations': <list(evaluations)>,
|
200
207
|
'rank_score_results': <list(rank_score_results)>,
|
201
208
|
}
|
202
209
|
|
@@ -340,6 +347,17 @@ def variant(
|
|
340
347
|
evaluation(store, evaluation_obj)
|
341
348
|
evaluations.append(evaluation_obj)
|
342
349
|
|
350
|
+
# Prepare classification information for visualisation
|
351
|
+
ccv_classification = variant_obj.get("ccv_classification")
|
352
|
+
if isinstance(ccv_classification, int):
|
353
|
+
ccv_code = CCV_MAP[variant_obj["ccv_classification"]]
|
354
|
+
variant_obj["ccv_classification"] = CCV_COMPLETE_MAP[ccv_code]
|
355
|
+
|
356
|
+
ccv_evaluations = []
|
357
|
+
for evaluation_obj in store.get_ccv_evaluations(variant_obj):
|
358
|
+
ccv_evaluation(store, evaluation_obj)
|
359
|
+
ccv_evaluations.append(evaluation_obj)
|
360
|
+
|
343
361
|
case_clinvars = store.case_to_clinVars(case_obj.get("display_name"))
|
344
362
|
|
345
363
|
if variant_id in case_clinvars:
|
@@ -379,12 +397,14 @@ def variant(
|
|
379
397
|
"dismiss_variant_options": dismiss_options,
|
380
398
|
"mosaic_variant_options": MOSAICISM_OPTIONS,
|
381
399
|
"ACMG_OPTIONS": ACMG_OPTIONS,
|
400
|
+
"CCV_OPTIONS": CCV_OPTIONS,
|
382
401
|
"case_tag_options": CASE_TAGS,
|
383
402
|
"inherit_palette": INHERITANCE_PALETTE,
|
384
403
|
"igv_tracks": get_igv_tracks("38" if variant_obj["is_mitochondrial"] else genome_build),
|
385
404
|
"has_rna_tracks": case_has_rna_tracks(case_obj),
|
386
405
|
"gens_info": gens.connection_settings(genome_build),
|
387
406
|
"evaluations": evaluations,
|
407
|
+
"ccv_evaluations": ccv_evaluations,
|
388
408
|
"rank_score_results": variant_rank_scores(store, case_obj, variant_obj),
|
389
409
|
}
|
390
410
|
|
@@ -442,7 +462,7 @@ def get_loqusdb_obs_cases(
|
|
442
462
|
obs_cases = []
|
443
463
|
user_institutes_ids = set([inst["_id"] for inst in user_institutes(store, current_user)])
|
444
464
|
for i, case_id in enumerate(obs_families):
|
445
|
-
if len(obs_cases) ==
|
465
|
+
if len(obs_cases) == 50:
|
446
466
|
break
|
447
467
|
if case_id == variant_obj["case_id"]:
|
448
468
|
continue
|
@@ -708,3 +728,130 @@ def variant_acmg_post(
|
|
708
728
|
criteria=criteria,
|
709
729
|
)
|
710
730
|
return classification
|
731
|
+
|
732
|
+
|
733
|
+
def variant_ccv(store: MongoAdapter, institute_id: str, case_name: str, variant_id: str):
|
734
|
+
"""Collect data relevant for rendering ClinGen-CCV-VIGG classification form.
|
735
|
+
|
736
|
+
Args:
|
737
|
+
store(scout.adapter.MongoAdapter)
|
738
|
+
institute_id(str): institute_obj['_id']
|
739
|
+
case_name(str): case_obj['display_name']
|
740
|
+
variant_id(str): variant_obj['document_id']
|
741
|
+
|
742
|
+
Returns:
|
743
|
+
data(dict): Things for the template
|
744
|
+
"""
|
745
|
+
variant_obj = store.variant(variant_id)
|
746
|
+
|
747
|
+
if not variant_obj:
|
748
|
+
return abort(404)
|
749
|
+
|
750
|
+
institute_obj, case_obj = variant_institute_and_case(
|
751
|
+
store, variant_obj, institute_id, case_name
|
752
|
+
)
|
753
|
+
|
754
|
+
return dict(
|
755
|
+
institute=institute_obj,
|
756
|
+
case=case_obj,
|
757
|
+
variant=variant_obj,
|
758
|
+
CRITERIA=CCV_CRITERIA,
|
759
|
+
CCV_OPTIONS=CCV_OPTIONS,
|
760
|
+
)
|
761
|
+
|
762
|
+
|
763
|
+
def check_reset_variant_ccv_classification(
|
764
|
+
store: MongoAdapter, evaluation_obj: dict, link: str
|
765
|
+
) -> bool:
|
766
|
+
"""Check if this was the last ClinGen-CGC-VIGG evaluation left on the variant.
|
767
|
+
If there is still a classification we want to remove the classification.
|
768
|
+
|
769
|
+
Args:
|
770
|
+
stores(cout.adapter.MongoAdapter)
|
771
|
+
evaluation_obj(dict): ClinGen-CGC-VIGG evaluation object
|
772
|
+
link(str): link for event
|
773
|
+
|
774
|
+
Returns: reset(bool) - True if classification reset was attempted
|
775
|
+
|
776
|
+
"""
|
777
|
+
|
778
|
+
if list(store.get_ccv_evaluations_case_specific(evaluation_obj["variant_specific"])):
|
779
|
+
return False
|
780
|
+
|
781
|
+
variant_obj = store.variant(document_id=evaluation_obj["variant_specific"])
|
782
|
+
|
783
|
+
if not variant_obj:
|
784
|
+
return abort(404)
|
785
|
+
|
786
|
+
ccv_classification = variant_obj.get("ccv_classification")
|
787
|
+
|
788
|
+
if not isinstance(ccv_classification, int):
|
789
|
+
return False
|
790
|
+
|
791
|
+
institute_obj, case_obj = variant_institute_and_case(
|
792
|
+
store,
|
793
|
+
variant_obj,
|
794
|
+
evaluation_obj["institute"]["_id"],
|
795
|
+
evaluation_obj["case"]["display_name"],
|
796
|
+
)
|
797
|
+
user_obj = store.user(current_user.email)
|
798
|
+
|
799
|
+
new_ccv = None
|
800
|
+
store.submit_ccv_evaluation(
|
801
|
+
variant_obj=variant_obj,
|
802
|
+
user_obj=user_obj,
|
803
|
+
institute_obj=institute_obj,
|
804
|
+
case_obj=case_obj,
|
805
|
+
link=link,
|
806
|
+
classification=new_ccv,
|
807
|
+
)
|
808
|
+
return True
|
809
|
+
|
810
|
+
|
811
|
+
def variant_ccv_post(
|
812
|
+
store: MongoAdapter,
|
813
|
+
institute_id: str,
|
814
|
+
case_name: str,
|
815
|
+
variant_id: str,
|
816
|
+
user_email: str,
|
817
|
+
criteria: list,
|
818
|
+
) -> dict:
|
819
|
+
"""Calculate an ClinGen-CGC-VICC classification based on a list of criteria.
|
820
|
+
|
821
|
+
Args:
|
822
|
+
store(scout.adapter.MongoAdapter)
|
823
|
+
institute_id(str): institute_obj['_id']
|
824
|
+
case_name(str): case_obj['display_name']
|
825
|
+
variant_id(str): variant_obj['document_id']
|
826
|
+
user_mail(str)
|
827
|
+
criteria()
|
828
|
+
|
829
|
+
Returns:
|
830
|
+
data(dict): Things for the template
|
831
|
+
|
832
|
+
"""
|
833
|
+
variant_obj = store.variant(variant_id)
|
834
|
+
|
835
|
+
if not variant_obj:
|
836
|
+
return abort(404)
|
837
|
+
|
838
|
+
institute_obj, case_obj = variant_institute_and_case(
|
839
|
+
store, variant_obj, institute_id, case_name
|
840
|
+
)
|
841
|
+
|
842
|
+
user_obj = store.user(user_email)
|
843
|
+
variant_link = url_for(
|
844
|
+
"variant.variant",
|
845
|
+
institute_id=institute_id,
|
846
|
+
case_name=case_name,
|
847
|
+
variant_id=variant_id,
|
848
|
+
)
|
849
|
+
classification = store.submit_ccv_evaluation(
|
850
|
+
institute_obj=institute_obj,
|
851
|
+
case_obj=case_obj,
|
852
|
+
variant_obj=variant_obj,
|
853
|
+
user_obj=user_obj,
|
854
|
+
link=variant_link,
|
855
|
+
criteria=criteria,
|
856
|
+
)
|
857
|
+
return classification
|