scout-browser 4.93.1__py3-none-any.whl → 4.95.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.
- scout/adapter/mongo/base.py +0 -0
- scout/adapter/mongo/hgnc.py +5 -1
- scout/adapter/mongo/managed_variant.py +4 -2
- scout/adapter/mongo/query.py +91 -54
- scout/adapter/mongo/variant.py +13 -8
- scout/build/panel.py +1 -1
- scout/commands/export/export_command.py +0 -0
- scout/commands/load/base.py +0 -0
- scout/commands/load/user.py +0 -0
- scout/commands/update/disease.py +0 -0
- scout/commands/update/genes.py +0 -0
- scout/commands/wipe_database.py +0 -0
- scout/constants/gene_tags.py +22 -12
- scout/demo/643594.research.mei.vcf.gz +0 -0
- scout/demo/643594.research.mei.vcf.gz.tbi +0 -0
- scout/load/panelapp.py +8 -12
- scout/parse/omim.py +5 -6
- scout/parse/panelapp.py +16 -42
- scout/parse/variant/compound.py +20 -21
- 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 +48 -0
- scout/server/blueprints/cases/templates/cases/case_report.html +17 -2
- scout/server/blueprints/cases/views.py +5 -5
- 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/utils.html +6 -5
- scout/server/blueprints/managed_variants/forms.py +17 -2
- scout/server/blueprints/managed_variants/templates/managed_variants/managed_variants.html +2 -2
- scout/server/blueprints/variant/templates/variant/components.html +27 -4
- scout/server/blueprints/variant/templates/variant/sv-variant.html +2 -2
- scout/server/blueprints/variant/templates/variant/tx_overview.html +3 -3
- scout/server/blueprints/variant/views.py +1 -2
- scout/server/blueprints/variants/forms.py +33 -5
- scout/server/blueprints/variants/templates/variants/cancer-sv-variants.html +4 -18
- scout/server/blueprints/variants/templates/variants/cancer-variants.html +2 -12
- scout/server/blueprints/variants/templates/variants/components.html +15 -1
- scout/server/blueprints/variants/templates/variants/sv-variants.html +2 -2
- scout/server/links.py +1 -1
- scout/utils/acmg.py +0 -1
- scout/utils/ccv.py +1 -9
- scout/utils/link.py +4 -3
- scout/utils/md5.py +0 -0
- {scout_browser-4.93.1.dist-info → scout_browser-4.95.0.dist-info}/METADATA +66 -45
- {scout_browser-4.93.1.dist-info → scout_browser-4.95.0.dist-info}/RECORD +41 -42
- {scout_browser-4.93.1.dist-info → scout_browser-4.95.0.dist-info}/WHEEL +1 -2
- scout/__version__.py +0 -1
- scout_browser-4.93.1.dist-info/top_level.txt +0 -1
- {scout_browser-4.93.1.dist-info → scout_browser-4.95.0.dist-info}/entry_points.txt +0 -0
- {scout_browser-4.93.1.dist-info → scout_browser-4.95.0.dist-info/licenses}/LICENSE +0 -0
@@ -668,6 +668,7 @@
|
|
668
668
|
<th>Inheritance models</th>
|
669
669
|
{% endif %}
|
670
670
|
<th>ACMG classification</th>
|
671
|
+
<th>Bayesian classification</th>
|
671
672
|
{% if cancer %}
|
672
673
|
<th>ClinGen-CGC-VICC classification</th>
|
673
674
|
{% endif %}
|
@@ -714,18 +715,32 @@
|
|
714
715
|
{% endif %}
|
715
716
|
<td>
|
716
717
|
{% if variant.acmg_classification %}
|
717
|
-
<span class="badge rounded-pill bg-{{variant.acmg_classification['color'] if variant.acmg_classification['color'] else 'secondary'}}" title="{{variant.acmg_classification['
|
718
|
+
<span class="badge rounded-pill bg-{{variant.acmg_classification['color'] if variant.acmg_classification['color'] else 'secondary'}}" title="{{variant.acmg_classification['label']}}">{{variant.acmg_classification['label'] }}</span>
|
719
|
+
{% else %}
|
720
|
+
-
|
721
|
+
{% endif %}
|
722
|
+
<td>
|
723
|
+
{% if variant.bayesian_acmg %}
|
724
|
+
<span class="badge rounded-pill bg-{{variant.bayesian_acmg.temperature_class}}">Score {{variant.bayesian_acmg.points|string}} <span class='fa {{variant.bayesian_acmg.temperature_icon}}'></span> {{variant.bayesian_acmg.temperature}} ({{variant.bayesian_acmg.point_classification}})</span>
|
718
725
|
{% else %}
|
719
726
|
-
|
720
727
|
{% endif %}
|
721
728
|
</td>
|
729
|
+
</td>
|
722
730
|
{% if cancer %}
|
723
731
|
<td>
|
724
732
|
{% if variant.ccv_classification %}
|
725
|
-
<span class="badge rounded-pill bg-{{variant.ccv_classification['color'] if variant.ccv_classification['color'] else 'secondary'}}" title="{{variant.ccv_classification['
|
733
|
+
<span class="badge rounded-pill bg-{{variant.ccv_classification['color'] if variant.ccv_classification['color'] else 'secondary'}}" title="{{variant.ccv_classification['label']}}">{{variant.ccv_classification['label'] }}</span>
|
726
734
|
{% else %}
|
727
735
|
-
|
728
736
|
{% endif %}
|
737
|
+
{% if variant.bayesian_ccv %}
|
738
|
+
<span class="badge rounded-pill bg-{{variant.bayesian_ccv.temperature_class}}">Score {{variant.bayesian_ccv.points|string}} <span class='fa {{variant.bayesian_ccv.temperature_icon}}'></span>
|
739
|
+
{% if variant.bayesian_ccv.point_classification == "VUS" %}
|
740
|
+
{{variant.bayesian_ccv.temperature}}
|
741
|
+
{% endif %}
|
742
|
+
</span>
|
743
|
+
{% endif %}
|
729
744
|
</td>
|
730
745
|
{% endif %}
|
731
746
|
</tr>
|
@@ -7,6 +7,7 @@ import shutil
|
|
7
7
|
from ast import literal_eval
|
8
8
|
from io import BytesIO
|
9
9
|
from operator import itemgetter
|
10
|
+
from tempfile import NamedTemporaryFile, mkdtemp
|
10
11
|
from typing import Generator, Optional, Union
|
11
12
|
|
12
13
|
from cairosvg import svg2png
|
@@ -286,7 +287,9 @@ def pdf_case_report(institute_id, case_name):
|
|
286
287
|
# Workaround to be able to print the case pedigree to pdf
|
287
288
|
if case_obj.get("madeline_info") and case_obj.get("madeline_info") != "":
|
288
289
|
try:
|
289
|
-
write_to =
|
290
|
+
write_to = NamedTemporaryFile(
|
291
|
+
mode="a+", prefix=case_obj.get("_id"), suffix="madeline.png"
|
292
|
+
)
|
290
293
|
svg2png(
|
291
294
|
bytestring=case_obj["madeline_info"],
|
292
295
|
write_to=write_to,
|
@@ -322,11 +325,8 @@ def mt_report(institute_id, case_name):
|
|
322
325
|
institute_obj, case_obj = institute_and_case(store, institute_id, case_name)
|
323
326
|
|
324
327
|
# create a temp folder to write excel files into
|
325
|
-
temp_excel_dir = os.path.join(
|
326
|
-
cases_bp.static_folder, "_".join([case_obj["display_name"], "mt_reports"])
|
327
|
-
)
|
328
|
-
os.makedirs(temp_excel_dir, exist_ok=True)
|
329
328
|
|
329
|
+
temp_excel_dir = mkdtemp(suffix="_".join([case_obj["display_name"], "mt_reports"]))
|
330
330
|
if controllers.mt_excel_files(store, case_obj, temp_excel_dir):
|
331
331
|
data = zip_dir_to_obj(temp_excel_dir)
|
332
332
|
|
@@ -399,11 +399,10 @@ def json_api_submission(submission_id):
|
|
399
399
|
afile.flush()
|
400
400
|
afile.seek(0)
|
401
401
|
|
402
|
-
with
|
403
|
-
mode="a+", prefix="Variant", suffix=".csv"
|
404
|
-
|
405
|
-
|
406
|
-
) as casedata_file:
|
402
|
+
with (
|
403
|
+
NamedTemporaryFile(mode="a+", prefix="Variant", suffix=".csv") as variant_file,
|
404
|
+
NamedTemporaryFile(mode="a+", prefix="CaseData", suffix=".csv") as casedata_file,
|
405
|
+
):
|
407
406
|
# Write temp Variant CSV file
|
408
407
|
_, variants_header, variants_lines = clinvar_submission_file(
|
409
408
|
submission_id, "variant_data", "SUB000"
|
@@ -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();
|
@@ -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") }}
|
@@ -10,6 +10,7 @@ from wtforms import (
|
|
10
10
|
SubmitField,
|
11
11
|
validators,
|
12
12
|
)
|
13
|
+
from wtforms.widgets import NumberInput
|
13
14
|
|
14
15
|
from scout.constants import CHROMOSOMES, SV_TYPES
|
15
16
|
|
@@ -24,8 +25,22 @@ CATEGORY_CHOICES = [
|
|
24
25
|
|
25
26
|
|
26
27
|
class ManagedVariantForm(FlaskForm):
|
27
|
-
position = IntegerField(
|
28
|
-
|
28
|
+
position = IntegerField(
|
29
|
+
"Start position",
|
30
|
+
[
|
31
|
+
validators.Optional(),
|
32
|
+
validators.NumberRange(min=0, message="Start position must be 1 or greater."),
|
33
|
+
],
|
34
|
+
widget=NumberInput(min=1),
|
35
|
+
)
|
36
|
+
end = IntegerField(
|
37
|
+
"End position",
|
38
|
+
[
|
39
|
+
validators.Optional(),
|
40
|
+
validators.NumberRange(min=0, message="End position must be 1 or greater."),
|
41
|
+
],
|
42
|
+
widget=NumberInput(min=1),
|
43
|
+
)
|
29
44
|
cytoband_start = SelectField("Cytoband start", choices=[])
|
30
45
|
cytoband_end = SelectField("Cytoband end", choices=[])
|
31
46
|
description = StringField(label="Description")
|
@@ -212,8 +212,8 @@
|
|
212
212
|
<tbody>
|
213
213
|
<tr>
|
214
214
|
<td>{{ add_form.chromosome(class_="form-control") }}</td>
|
215
|
-
<td><input type="number" name="position" id="position" class="form-control" required></td>
|
216
|
-
<td><input type="number" name="end" id="end" class="form-control"></td>
|
215
|
+
<td><input type="number" name="position" id="position" class="form-control" required min="1"></td>
|
216
|
+
<td><input type="number" name="end" id="end" class="form-control" min="1"></td>
|
217
217
|
<td><input type="text" name="reference" id="reference" class="form-control" required></td>
|
218
218
|
<td><input type="text" name="alternative" id="alternative" class="form-control" required></td>
|
219
219
|
<td>{{ add_form.category(class_="form-control", onchange="populateSubTypeSelect('add_variant')") }}</td>
|
@@ -38,9 +38,13 @@
|
|
38
38
|
<li class="list-group-item {{ 'list-group-item-info' if current_variant }}">
|
39
39
|
<div class="d-flex">
|
40
40
|
<span>
|
41
|
-
|
41
|
+
{% if variant.category in ["snv", "cancer_snv"] %}
|
42
|
+
<a href="{{ url_for('variant.evaluation', evaluation_id=data._id) }}">
|
43
|
+
{{ data.classification.label }}
|
44
|
+
</a>
|
45
|
+
{% else %}
|
42
46
|
{{ data.classification.label }}
|
43
|
-
|
47
|
+
{% endif %}
|
44
48
|
<span class="badge bg-info">{{ data.classification.short }}</span>
|
45
49
|
</span>
|
46
50
|
<span>
|
@@ -133,7 +137,6 @@
|
|
133
137
|
{% endmacro %}
|
134
138
|
|
135
139
|
{% macro acmg_form(institute, case, variant, ACMG_OPTIONS, selected=None) %}
|
136
|
-
<label class="mt-3" data-bs-toggle="tooltip" title="Richards et al 2015"><a href="https://www.acmg.net/docs/standards_guidelines_for_the_interpretation_of_sequence_variants.pdf" rel="noopener noreferrer" target="_blank" style="color: inherit; text-decoration: inherit;">ACMG classification</a></label>
|
137
140
|
<form action="{{ url_for('variant.variant_update', institute_id=institute._id, case_name=case.display_name, variant_id=variant._id) }}" method="POST">
|
138
141
|
<div class="d-flex justify-content-between">
|
139
142
|
{% for option in ACMG_OPTIONS %}
|
@@ -168,7 +171,7 @@
|
|
168
171
|
<form action="{{ url_for('variant.ccv_evaluation', evaluation_id=data._id) }}" method="POST" style="display: inline-block;">
|
169
172
|
<button class="btn btn-xs btn-link" >Delete</button>
|
170
173
|
</form>
|
171
|
-
{% if data.ccv_criteria %}
|
174
|
+
{% if data.ccv_criteria %}
|
172
175
|
<a class="btn btn-xs btn-link" href="{{ url_for('variant.ccv_evaluation', evaluation_id=data._id, edit=True) }}" data-bs-toggle="tooltip" title="Editing this classification will result in a new classification">Edit</a>
|
173
176
|
{% endif %}
|
174
177
|
{% endif %}
|
@@ -191,6 +194,25 @@
|
|
191
194
|
</form>
|
192
195
|
{% endmacro %}
|
193
196
|
|
197
|
+
{% macro panel_classify_sv(variant, institute, case, ACMG_OPTIONS, evaluations) %}
|
198
|
+
<div class="mt-3">
|
199
|
+
ACMG (INDEL) <a href="https://www.acmg.net/docs/standards_guidelines_for_the_interpretation_of_sequence_variants.pdf" rel="noopener noreferrer" target="_blank" style="text-decoration: inherit;" data-bs-toggle="tooltip" title="Richards et al 2015">classification</a>
|
200
|
+
</div>
|
201
|
+
<div>
|
202
|
+
{{ acmg_form(institute, case, variant, ACMG_OPTIONS, variant.acmg_classification.code if variant.acmg_classification) }}
|
203
|
+
</div>
|
204
|
+
<div class="mt-3">
|
205
|
+
ACMG SV classification <a href="https://pubmed.ncbi.nlm.nih.gov/31690835/" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer">guidelines</a> & <a href="https://cnvcalc.clinicalgenome.org/cnvcalc/" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer">guide</a>.
|
206
|
+
</div>
|
207
|
+
{% if evaluations %} <!-- scrollable previous ACMG evaluations div-->
|
208
|
+
<div class="list-group mt-3" style="max-height:200px;overflow-y: scroll;">
|
209
|
+
{% for evaluation in evaluations %}
|
210
|
+
{{ acmg_classification_item(variant, evaluation) }}
|
211
|
+
{% endfor %}
|
212
|
+
</div>
|
213
|
+
{% endif %}
|
214
|
+
{% endmacro %}
|
215
|
+
|
194
216
|
{% macro panel_classify(variant, institute, case, ACMG_OPTIONS, CCV_OPTIONS, manual_rank_options, cancer_tier_options, dismiss_variant_options, mosaic_variant_options, evaluations, ccv_evaluations) %}
|
195
217
|
<div class="card panel-default">
|
196
218
|
<div class="panel-heading">Classify</div>
|
@@ -203,6 +225,7 @@
|
|
203
225
|
{% if case.track != "cancer" %}
|
204
226
|
{{ mosaic_variant_button(variant, institute, case, mosaic_variant_options) }}
|
205
227
|
{% endif %}
|
228
|
+
ACMG <a href="https://www.acmg.net/docs/standards_guidelines_for_the_interpretation_of_sequence_variants.pdf" rel="noopener noreferrer" target="_blank" style="text-decoration: inherit;" data-bs-toggle="tooltip" title="Richards et al 2015">classification</a>
|
206
229
|
{{ acmg_form(institute, case, variant, ACMG_OPTIONS, variant.acmg_classification.code if variant.acmg_classification) }}
|
207
230
|
<div class="mt-3">
|
208
231
|
<a href="{{ url_for('variant.variant_acmg', institute_id=institute._id, case_name=case.display_name, variant_id=variant._id) }}" class="btn btn-outline-secondary form-control">Classify</a>
|
@@ -1,7 +1,7 @@
|
|
1
1
|
{% extends "layout.html" %}
|
2
2
|
{% from "utils.html" import activity_panel, comments_panel, pedigree_panel %}
|
3
3
|
{% from "variant/variant_details.html" import old_observations %}
|
4
|
-
{% from "variant/components.html" import external_scripts, external_stylesheets, matching_variants, variant_scripts %}
|
4
|
+
{% from "variant/components.html" import external_scripts, external_stylesheets, matching_variants, panel_classify_sv, variant_scripts %}
|
5
5
|
{% from "variant/utils.html" import causative_button, genes_panel, igv_track_selection, modal_causative, overlapping_panel, sv_alignments, pin_button, transcripts_panel, custom_annotations, gene_panels %}
|
6
6
|
{% from "variant/rank_score_results.html" import rankscore_panel %}
|
7
7
|
{% from "variant/gene_disease_relations.html" import orpha_omim_phenotypes %}
|
@@ -196,7 +196,7 @@
|
|
196
196
|
{{ variant_tag_button(variant, institute, case, manual_rank_options) }}
|
197
197
|
</div>
|
198
198
|
<div>
|
199
|
-
|
199
|
+
{{ panel_classify_sv(variant, institute, case, ACMG_OPTIONS, evaluations) }}
|
200
200
|
</div>
|
201
201
|
</div>
|
202
202
|
<div class="list-group mt-3">
|
@@ -76,15 +76,15 @@
|
|
76
76
|
</td> <!-- end of ID col-->
|
77
77
|
|
78
78
|
<td> <!-- HGVS Description col -->
|
79
|
-
{% set hgvs_c = (tx.coding_sequence_name or '')|truncate(
|
79
|
+
{% set hgvs_c = (tx.coding_sequence_name or '')|truncate(30, True) %}
|
80
80
|
{% if variant.chromosome in ["MT","M"] %}
|
81
81
|
{% set mt_notation = "m." ~ variant.position ~ variant.reference ~ ">" ~ variant.alternative %}
|
82
|
-
{{ mt_notation|truncate(
|
82
|
+
{{ mt_notation|truncate(30,True) }} <span class="text-muted">({{ hgvs_c }})</span>
|
83
83
|
{% else %}
|
84
84
|
{{ hgvs_c }}
|
85
85
|
{% endif %}
|
86
86
|
<span class="text-muted float-end">
|
87
|
-
{{ (tx.protein_sequence_name or '')|url_decode }}
|
87
|
+
{{ (tx.protein_sequence_name or '')|url_decode|truncate(30, True) }}
|
88
88
|
</span>
|
89
89
|
</td> <!-- end of HGVS Description col -->
|
90
90
|
|
@@ -374,7 +374,6 @@ def variant_update(institute_id, case_name, variant_id):
|
|
374
374
|
@templated("variant/acmg.html")
|
375
375
|
def evaluation(evaluation_id):
|
376
376
|
"""Show, edit or delete an ACMG evaluation."""
|
377
|
-
|
378
377
|
evaluation_obj = store.get_evaluation(evaluation_id)
|
379
378
|
if evaluation_obj is None:
|
380
379
|
flash("Evaluation was not found in database", "warning")
|
@@ -392,7 +391,7 @@ def evaluation(evaluation_id):
|
|
392
391
|
if check_reset_variant_classification(store, evaluation_obj, link):
|
393
392
|
flash("Cleared ACMG classification.", "info")
|
394
393
|
|
395
|
-
return redirect(
|
394
|
+
return redirect(request.referrer)
|
396
395
|
|
397
396
|
return dict(
|
398
397
|
evaluation=evaluation_obj,
|