scout-browser 4.82.2__py3-none-any.whl → 4.83__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/client.py +1 -0
- scout/adapter/mongo/base.py +0 -1
- scout/adapter/mongo/case.py +15 -37
- scout/adapter/mongo/case_events.py +98 -2
- scout/adapter/mongo/hgnc.py +39 -22
- scout/adapter/mongo/institute.py +3 -9
- scout/adapter/mongo/panel.py +2 -1
- scout/adapter/mongo/variant.py +3 -2
- scout/adapter/mongo/variant_loader.py +92 -79
- scout/commands/base.py +1 -0
- scout/commands/update/case.py +10 -10
- scout/commands/update/individual.py +6 -1
- scout/constants/file_types.py +4 -0
- scout/load/__init__.py +0 -1
- scout/load/all.py +3 -4
- scout/load/panel.py +8 -4
- scout/load/setup.py +1 -0
- scout/models/case/case_loading_models.py +6 -16
- scout/parse/case.py +0 -1
- scout/parse/disease_terms.py +1 -0
- scout/parse/omim.py +1 -0
- scout/parse/panel.py +40 -15
- scout/resources/__init__.py +3 -0
- scout/server/app.py +4 -50
- scout/server/blueprints/alignviewers/controllers.py +15 -17
- scout/server/blueprints/alignviewers/templates/alignviewers/igv_viewer.html +13 -3
- scout/server/blueprints/alignviewers/views.py +10 -15
- scout/server/blueprints/cases/controllers.py +70 -73
- scout/server/blueprints/cases/templates/cases/case.html +37 -21
- scout/server/blueprints/cases/templates/cases/collapsible_actionbar.html +1 -1
- scout/server/blueprints/cases/templates/cases/phenotype.html +8 -6
- scout/server/blueprints/cases/templates/cases/utils.html +3 -3
- scout/server/blueprints/cases/views.py +8 -6
- scout/server/blueprints/variant/controllers.py +5 -5
- scout/server/blueprints/variant/templates/variant/acmg.html +25 -16
- scout/server/blueprints/variant/templates/variant/components.html +11 -6
- scout/server/blueprints/variant/views.py +5 -2
- scout/server/blueprints/variants/controllers.py +1 -1
- scout/server/blueprints/variants/views.py +1 -1
- scout/server/config.py +16 -4
- scout/server/extensions/__init__.py +4 -2
- scout/server/extensions/beacon_extension.py +1 -0
- scout/server/extensions/chanjo_extension.py +58 -0
- scout/server/extensions/phenopacket_extension.py +1 -0
- scout/server/static/bs_styles.css +18 -0
- scout/server/utils.py +16 -2
- scout/utils/acmg.py +33 -20
- scout/utils/track_resources.py +70 -0
- {scout_browser-4.82.2.dist-info → scout_browser-4.83.dist-info}/METADATA +1 -1
- {scout_browser-4.82.2.dist-info → scout_browser-4.83.dist-info}/RECORD +55 -55
- scout/load/case.py +0 -36
- scout/utils/cloud_resources.py +0 -61
- {scout_browser-4.82.2.dist-info → scout_browser-4.83.dist-info}/LICENSE +0 -0
- {scout_browser-4.82.2.dist-info → scout_browser-4.83.dist-info}/WHEEL +0 -0
- {scout_browser-4.82.2.dist-info → scout_browser-4.83.dist-info}/entry_points.txt +0 -0
- {scout_browser-4.82.2.dist-info → scout_browser-4.83.dist-info}/top_level.txt +0 -0
@@ -48,6 +48,7 @@ from scout.server.utils import (
|
|
48
48
|
case_has_chanjo2_coverage,
|
49
49
|
case_has_chanjo_coverage,
|
50
50
|
case_has_mt_alignments,
|
51
|
+
case_has_mtdna_report,
|
51
52
|
case_has_rna_tracks,
|
52
53
|
institute_and_case,
|
53
54
|
)
|
@@ -62,6 +63,8 @@ JSON_HEADERS = {
|
|
62
63
|
|
63
64
|
COVERAGE_REPORT_TIMEOUT = 20
|
64
65
|
|
66
|
+
PANEL_PROJECTION = {"version": 1, "display_name": 1, "genes": 1}
|
67
|
+
|
65
68
|
|
66
69
|
def phenomizer_diseases(hpo_ids, case_obj, p_value_treshold=1):
|
67
70
|
"""Return the list of HGNC symbols that match annotated HPO terms on Phenomizer
|
@@ -278,22 +281,16 @@ def sma_case(store, institute_obj, case_obj):
|
|
278
281
|
return data
|
279
282
|
|
280
283
|
|
281
|
-
def case(
|
284
|
+
def case(
|
285
|
+
store: MongoAdapter, institute_obj: dict, case_obj: dict, hide_matching: bool = True
|
286
|
+
) -> dict:
|
282
287
|
"""Preprocess a single case.
|
283
288
|
|
284
289
|
Prepare the case to be displayed in the case view.
|
285
290
|
|
286
|
-
|
287
|
-
store(adapter.MongoAdapter)
|
288
|
-
institute_obj(models.Institute)
|
289
|
-
case_obj(models.Case)
|
290
|
-
|
291
|
-
Returns:
|
292
|
-
data(dict): includes the cases, how many there are and the limit.
|
293
|
-
|
291
|
+
The return data dict includes the cases, how many there are and the limit.
|
294
292
|
"""
|
295
293
|
# Convert individual information to more readable format
|
296
|
-
|
297
294
|
_populate_case_individuals(case_obj)
|
298
295
|
|
299
296
|
case_obj["assignees"] = [
|
@@ -305,6 +302,7 @@ def case(store, institute_obj, case_obj):
|
|
305
302
|
case_has_mt_alignments(case_obj)
|
306
303
|
case_has_chanjo_coverage(case_obj)
|
307
304
|
case_has_chanjo2_coverage(case_obj)
|
305
|
+
case_has_mtdna_report(case_obj)
|
308
306
|
|
309
307
|
case_groups = {}
|
310
308
|
case_group_label = {}
|
@@ -395,31 +393,40 @@ def case(store, institute_obj, case_obj):
|
|
395
393
|
"case_images", case_obj["custom_images"].get("case", {})
|
396
394
|
)
|
397
395
|
|
398
|
-
|
399
|
-
|
396
|
+
other_causatives = []
|
397
|
+
other_causatives_in_default_panels = []
|
398
|
+
default_managed_variants = []
|
399
|
+
managed_variants = []
|
400
400
|
|
401
|
-
|
402
|
-
|
403
|
-
|
401
|
+
if hide_matching is False:
|
402
|
+
# Limit secondary findings according to institute settings
|
403
|
+
limit_genes = store.safe_genes_filter(institute_obj["_id"])
|
404
404
|
|
405
|
-
|
406
|
-
|
407
|
-
|
405
|
+
limit_genes_default_panels = _limit_genes_on_default_panels(
|
406
|
+
case_obj["default_genes"], limit_genes
|
407
|
+
)
|
408
408
|
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
"managed_variants": [
|
409
|
+
other_causatives, other_causatives_in_default_panels = _matching_causatives(
|
410
|
+
store, case_obj, limit_genes, limit_genes_default_panels
|
411
|
+
)
|
412
|
+
|
413
|
+
managed_variants = [
|
415
414
|
var for var in store.check_managed(case_obj=case_obj, limit_genes=limit_genes)
|
416
|
-
]
|
417
|
-
|
415
|
+
]
|
416
|
+
default_managed_variants = [
|
418
417
|
var
|
419
418
|
for var in store.check_managed(
|
420
419
|
case_obj=case_obj, limit_genes=limit_genes_default_panels
|
421
420
|
)
|
422
|
-
]
|
421
|
+
]
|
422
|
+
|
423
|
+
data = {
|
424
|
+
"institute": institute_obj,
|
425
|
+
"case": case_obj,
|
426
|
+
"other_causatives": other_causatives,
|
427
|
+
"default_other_causatives": other_causatives_in_default_panels,
|
428
|
+
"managed_variants": managed_variants,
|
429
|
+
"default_managed_variants": default_managed_variants,
|
423
430
|
"comments": store.events(institute_obj, case=case_obj, comments=True),
|
424
431
|
"hpo_groups": pheno_groups,
|
425
432
|
"case_groups": case_groups,
|
@@ -441,6 +448,7 @@ def case(store, institute_obj, case_obj):
|
|
441
448
|
"mme_nodes": matchmaker.connected_nodes,
|
442
449
|
"gens_info": gens.connection_settings(case_obj.get("genome_build")),
|
443
450
|
"display_rerunner": rerunner.connection_settings.get("display", False),
|
451
|
+
"hide_matching": hide_matching,
|
444
452
|
}
|
445
453
|
|
446
454
|
return data
|
@@ -496,8 +504,12 @@ def _get_default_panel_genes(store: MongoAdapter, case_obj: dict) -> list:
|
|
496
504
|
continue
|
497
505
|
panel_name = panel_info["panel_name"]
|
498
506
|
panel_version = panel_info.get("version")
|
499
|
-
panel_obj = store.gene_panel(
|
500
|
-
|
507
|
+
panel_obj = store.gene_panel(
|
508
|
+
panel_name,
|
509
|
+
version=panel_version,
|
510
|
+
projection=PANEL_PROJECTION,
|
511
|
+
)
|
512
|
+
latest_panel = store.gene_panel(panel_name, projection=PANEL_PROJECTION)
|
501
513
|
panel_info["removed"] = False if latest_panel is None else latest_panel.get("hidden", False)
|
502
514
|
if not panel_obj:
|
503
515
|
panel_obj = latest_panel
|
@@ -555,19 +567,17 @@ def check_outdated_gene_panel(panel_obj, latest_panel):
|
|
555
567
|
missing_genes, extra_genes
|
556
568
|
"""
|
557
569
|
# Create a list of minified gene object for the case panel {hgnc_id, gene_symbol}
|
558
|
-
case_panel_genes = [
|
559
|
-
{"hgnc_id": gene["hgnc_id"], "symbol": gene.get("symbol", gene["hgnc_id"])}
|
560
|
-
for gene in panel_obj["genes"]
|
561
|
-
]
|
570
|
+
case_panel_genes = set([gene.get("symbol", gene["hgnc_id"]) for gene in panel_obj["genes"]])
|
562
571
|
# And for the latest panel
|
563
|
-
latest_panel_genes =
|
564
|
-
|
565
|
-
|
566
|
-
]
|
572
|
+
latest_panel_genes = set(
|
573
|
+
[gene.get("symbol", gene["hgnc_id"]) for gene in latest_panel["genes"]]
|
574
|
+
)
|
567
575
|
# Extract the genes unique to case panel
|
568
|
-
extra_genes =
|
576
|
+
extra_genes = case_panel_genes.difference(latest_panel_genes)
|
577
|
+
|
569
578
|
# Extract the genes unique to latest panel
|
570
|
-
missing_genes =
|
579
|
+
missing_genes = latest_panel_genes.difference(case_panel_genes)
|
580
|
+
|
571
581
|
return extra_genes, missing_genes
|
572
582
|
|
573
583
|
|
@@ -674,7 +684,7 @@ def case_report_content(store: MongoAdapter, institute_obj: dict, case_obj: dict
|
|
674
684
|
return data
|
675
685
|
|
676
686
|
|
677
|
-
def mt_coverage_stats(individuals, ref_chrom="14"):
|
687
|
+
def mt_coverage_stats(individuals, ref_chrom="14") -> dict:
|
678
688
|
"""Send a request to chanjo report endpoint to retrieve MT vs autosome coverage stats
|
679
689
|
|
680
690
|
Args:
|
@@ -732,10 +742,9 @@ def mt_excel_files(store, case_obj, temp_excel_dir):
|
|
732
742
|
"""
|
733
743
|
today = datetime.datetime.now().strftime(DATE_DAY_FORMATTER)
|
734
744
|
samples = case_obj.get("individuals")
|
735
|
-
file_header = MT_EXPORT_HEADER
|
736
745
|
coverage_stats = None
|
737
746
|
# if chanjo connection is established, include MT vs AUTOSOME coverage stats
|
738
|
-
if current_app.config.get("
|
747
|
+
if current_app.config.get("chanjo_report"):
|
739
748
|
coverage_stats = mt_coverage_stats(samples)
|
740
749
|
|
741
750
|
query = {"chrom": "MT"}
|
@@ -765,9 +774,8 @@ def mt_excel_files(store, case_obj, temp_excel_dir):
|
|
765
774
|
for col, field in enumerate(line): # each field in line becomes a cell
|
766
775
|
Report_Sheet.write(row, col, field)
|
767
776
|
|
768
|
-
if
|
769
|
-
|
770
|
-
): # it's None if app is not connected to Chanjo or {} if samples are not in Chanjo db
|
777
|
+
# coverage_stats is None if app is not connected to Chanjo or {} if samples are not in Chanjo db
|
778
|
+
if coverage_stats and sample_id in coverage_stats:
|
771
779
|
# Write coverage stats header after introducing 2 empty lines
|
772
780
|
for col, field in enumerate(MT_COV_STATS_HEADER):
|
773
781
|
Report_Sheet.write(row + 3, col, field)
|
@@ -805,27 +813,6 @@ def update_synopsis(store, institute_obj, case_obj, user_obj, new_synopsis):
|
|
805
813
|
store.update_synopsis(institute_obj, case_obj, user_obj, link, content=new_synopsis)
|
806
814
|
|
807
815
|
|
808
|
-
def _update_case(store, case_obj, user_obj, institute_obj, verb):
|
809
|
-
"""Update case with new sample data, and create an associated event"""
|
810
|
-
store.update_case(case_obj, keep_date=True)
|
811
|
-
|
812
|
-
link = url_for(
|
813
|
-
"cases.case",
|
814
|
-
institute_id=institute_obj["_id"],
|
815
|
-
case_name=case_obj["display_name"],
|
816
|
-
)
|
817
|
-
|
818
|
-
store.create_event(
|
819
|
-
institute=institute_obj,
|
820
|
-
case=case_obj,
|
821
|
-
user=user_obj,
|
822
|
-
link=link,
|
823
|
-
category="case",
|
824
|
-
verb=verb,
|
825
|
-
subject=case_obj["display_name"],
|
826
|
-
)
|
827
|
-
|
828
|
-
|
829
816
|
def update_individuals(store, institute_obj, case_obj, user_obj, ind, age, tissue):
|
830
817
|
"""Handle update of individual data (age and/or Tissue type) for a case"""
|
831
818
|
|
@@ -841,8 +828,13 @@ def update_individuals(store, institute_obj, case_obj, user_obj, ind, age, tissu
|
|
841
828
|
|
842
829
|
case_obj["individuals"] = case_individuals
|
843
830
|
|
844
|
-
|
845
|
-
|
831
|
+
link = url_for(
|
832
|
+
"cases.case",
|
833
|
+
institute_id=institute_obj["_id"],
|
834
|
+
case_name=case_obj["display_name"],
|
835
|
+
)
|
836
|
+
|
837
|
+
store.update_case_individual(case_obj, user_obj, institute_obj, link)
|
846
838
|
|
847
839
|
|
848
840
|
def update_cancer_samples(
|
@@ -866,8 +858,13 @@ def update_cancer_samples(
|
|
866
858
|
|
867
859
|
case_obj["individuals"] = case_samples
|
868
860
|
|
869
|
-
|
870
|
-
|
861
|
+
link = url_for(
|
862
|
+
"cases.case",
|
863
|
+
institute_id=institute_obj["_id"],
|
864
|
+
case_name=case_obj["display_name"],
|
865
|
+
)
|
866
|
+
|
867
|
+
store.update_case_sample(case_obj, user_obj, institute_obj, link)
|
871
868
|
|
872
869
|
|
873
870
|
def _all_hpo_gene_list_genes(
|
@@ -1431,13 +1428,13 @@ def _matching_causatives(
|
|
1431
1428
|
other_causatives_in_default_panels = []
|
1432
1429
|
|
1433
1430
|
for causative in matching_causatives:
|
1434
|
-
hgnc_ids =
|
1431
|
+
hgnc_ids = {gene.get("hgnc_id") for gene in causative.get("genes", [])}
|
1435
1432
|
# Fetch all matching causatives if no causatives_filter defined
|
1436
1433
|
# or only causatives matching the filter:
|
1437
|
-
if not other_causatives_filter or (
|
1434
|
+
if not other_causatives_filter or (hgnc_ids & set(other_causatives_filter)):
|
1438
1435
|
other_causatives.append(causative)
|
1439
1436
|
# Only matching causatives in default gene panels:
|
1440
|
-
if
|
1437
|
+
if hgnc_ids & set(other_causatives_in_default_panels_filter):
|
1441
1438
|
other_causatives_in_default_panels.append(causative)
|
1442
1439
|
|
1443
1440
|
return other_causatives, other_causatives_in_default_panels
|
@@ -120,30 +120,46 @@
|
|
120
120
|
</div>
|
121
121
|
|
122
122
|
<div class="card panel-default" >
|
123
|
-
{% if other_causatives|length > 0%}
|
124
123
|
<div class="row mt-0">
|
125
|
-
<div class="col-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
<div class="col-sm-12 col-md-12">{{ matching_managed_variants(managed_variants, institute, case) }}</div>
|
137
|
-
</div>
|
138
|
-
{% endif %}
|
139
|
-
|
140
|
-
{% if default_managed_variants|length > 0%}
|
141
|
-
<div class="row mt-0">
|
142
|
-
<div class="col-sm-12 col-md-12">{{ matching_managed_variants(default_managed_variants, institute, case, default=True) }}</div>
|
124
|
+
<div class="col-sm-12 col-md-12 ms-3">
|
125
|
+
<div data-bs-toggle='tooltip' class="panel-heading" title="Check if there are any variants in this case
|
126
|
+
marked as causative in another case for this institute, or are on the managed variants list.">
|
127
|
+
<strong>{% if hide_matching == false %}
|
128
|
+
<a href="{{ url_for('cases.case', institute_id=institute._id, case_name=case.display_name, hide_matching='True') }}" class="text-body"><span class="me-1 fa fa-caret-right"></span>Search for matching causatives and managed variants</a>
|
129
|
+
{% else %}
|
130
|
+
<a href="{{ url_for('cases.case', institute_id=institute._id, case_name=case.display_name, hide_matching='False') }}" class="text-body"><span class="me-1 fa fa-caret-down"></span>Search for matching causatives and managed variants</a>
|
131
|
+
{% endif %}
|
132
|
+
</strong>
|
133
|
+
</div>
|
134
|
+
</div>
|
143
135
|
</div>
|
136
|
+
{% if hide_matching == false %}
|
137
|
+
{% if other_causatives|length > 0 %}
|
138
|
+
<div class="row mt-0 ms-3">
|
139
|
+
<div class="col-xs-12 col-md-12 ms-3">{{ matching_causatives(other_causatives, institute, case) }}</div>
|
140
|
+
</div>
|
141
|
+
{% endif %}
|
142
|
+
{% if default_other_causatives|length > 0%}
|
143
|
+
<div class="row mt-0 ms-3">
|
144
|
+
<div class="col-xs-12 col-md-12 ms-3">{{ matching_causatives(default_other_causatives, institute, case, default=True) }}</div>
|
145
|
+
</div>
|
146
|
+
{% endif %}
|
147
|
+
{% if managed_variants|length > 0%}
|
148
|
+
<div class="row mt-0 ms-3">
|
149
|
+
<div class="col-sm-12 col-md-12 ms-3">{{ matching_managed_variants(managed_variants, institute, case) }}</div>
|
150
|
+
</div>
|
151
|
+
{% endif %}
|
152
|
+
{% if default_managed_variants|length > 0%}
|
153
|
+
<div class="row mt-0 ms-3">
|
154
|
+
<div class="col-sm-12 col-md-12 ms-3">{{ matching_managed_variants(default_managed_variants, institute, case, default=True) }}</div>
|
155
|
+
</div>
|
156
|
+
{% endif %}
|
157
|
+
{% if other_causatives|length == 0 and default_other_causatives|length == 0 and managed_variants|length == 0 and default_managed_variants|length == 0%}
|
158
|
+
<div class="row mt-0 ms-3">
|
159
|
+
<div class="col-sm-12 col-md-12 ms-3">No matching causatives or managed variants found</div>
|
160
|
+
</div>
|
161
|
+
{% endif %}
|
144
162
|
{% endif %}
|
145
|
-
</div>
|
146
|
-
|
147
163
|
<div class="row">
|
148
164
|
<div class="col">{{ causatives_list(causatives, partial_causatives, evaluated_variants, institute, case, manual_rank_options, cancer_tier_options) }}</div>
|
149
165
|
<div class="col">{{ suspects_list(suspects, institute, case, manual_rank_options, cancer_tier_options) }}</div>
|
@@ -108,7 +108,7 @@
|
|
108
108
|
{% endif %}
|
109
109
|
|
110
110
|
<!-- Display mtDNA report for non-cancer cases -->
|
111
|
-
{% if case.
|
111
|
+
{% if case.mtdna_report %}
|
112
112
|
<div href="#" class="bg-dark list-group-item text-white">
|
113
113
|
<div class="d-flex flex-row flex-fill bd-highlight">
|
114
114
|
<div>
|
@@ -236,11 +236,13 @@
|
|
236
236
|
<!-- Display and remove added HPO terms -->
|
237
237
|
<div class="row mt-3">
|
238
238
|
<div class="col-12 ms-3">
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
239
|
+
{% if "phenotype_terms" in case and case.phenotype_terms|length > 0 %}
|
240
|
+
{% for hpo_term in case.phenotype_terms %}
|
241
|
+
{{ hpo_item(hpo_term, case) }}
|
242
|
+
{% endfor %}
|
243
|
+
{% else %}
|
244
|
+
<span class="text-mute">No phenotypes added yet</span>
|
245
|
+
{% endif %}
|
244
246
|
</div>
|
245
247
|
</div>
|
246
248
|
|
@@ -251,7 +253,7 @@
|
|
251
253
|
<input class="ms-3" name="min_match" type="number" min="0" step="1" placeholder="Min matches" style="width:130px"/>
|
252
254
|
{% if config.PHENOMIZER_USERNAME %}
|
253
255
|
<button class="btn btn-secondary btn-sm" type="submit" name="action" value="PHENOMIZER"
|
254
|
-
{%if case.phenotype_terms|length == 0 %} disabled {%endif%}>Phenomizer</button>
|
256
|
+
{% if "phenotype_terms" not in case or case.phenotype_terms|length == 0 %} disabled {% endif %}>Phenomizer</button>
|
255
257
|
{% endif %}
|
256
258
|
</div>
|
257
259
|
<div class="col-1">
|
@@ -633,8 +633,8 @@
|
|
633
633
|
|
634
634
|
{% macro matching_causatives(other_causatives, institute, case, default=False) %}
|
635
635
|
<div data-bs-toggle='tooltip' class="panel-heading" title="If there are any variants in this case
|
636
|
-
|
637
|
-
<strong><a data-bs-toggle="collapse" href="#matchingCausatives{% if default %}Default{% endif %}" class="text-body">Matching causatives {% if default %}in case default panels{% endif %}({{other_causatives|length}})</a></strong>
|
636
|
+
matching a causative in another case for this institute. {% if default %}Variants in default panels for the case only.{% endif %}">
|
637
|
+
<strong><a data-bs-toggle="collapse" href="#matchingCausatives{% if default %}Default{% endif %}" class="text-body" aria-expanded="false"><span class="collapse-icon me-1"></span>Matching causatives {% if default %}in case default panels{% endif %}({{other_causatives|length}})</a></strong>
|
638
638
|
</div>
|
639
639
|
<ul class="list-group collapse" id="matchingCausatives{% if default %}Default{% endif %}">
|
640
640
|
{% for variant in other_causatives %}
|
@@ -650,7 +650,7 @@
|
|
650
650
|
{% macro matching_managed_variants(managed_variants, institute, case, default=False) %}
|
651
651
|
<div data-bs-toggle='tooltip' class="panel-heading" title="Any variants in this case
|
652
652
|
that have been marked as managed. {% if default %}Variants in default panels for the case only.{% endif %}">
|
653
|
-
<strong><a data-bs-toggle="collapse" class="text-body" href="#matchingManaged{% if default %}Default{% endif %}">Managed variants {% if default %}in case default panels{% endif %}({{managed_variants|length}})</a></strong>
|
653
|
+
<strong><a data-bs-toggle="collapse" class="text-body" href="#matchingManaged{% if default %}Default{% endif %}" aria-expanded="false"><span class="collapse-icon me-1"></span>Managed variants {% if default %}in case default panels{% endif %}({{managed_variants|length}})</a></strong>
|
654
654
|
</div>
|
655
655
|
<ul class="list-group">
|
656
656
|
{% for variant in managed_variants %}
|
@@ -4,6 +4,7 @@ import json
|
|
4
4
|
import logging
|
5
5
|
import os.path
|
6
6
|
import shutil
|
7
|
+
from ast import literal_eval
|
7
8
|
from io import BytesIO
|
8
9
|
from operator import itemgetter
|
9
10
|
from typing import Generator, Optional, Union
|
@@ -86,7 +87,6 @@ def case(
|
|
86
87
|
So do case_id, but we still call institute_and_case again to fetch institute
|
87
88
|
and reuse its user access verification.
|
88
89
|
"""
|
89
|
-
|
90
90
|
if case_id:
|
91
91
|
case_obj = store.case(case_id=case_id, projection={"display_name": 1, "owner": 1})
|
92
92
|
|
@@ -102,7 +102,12 @@ def case(
|
|
102
102
|
flash("Case {} does not exist in database!".format(case_name))
|
103
103
|
return redirect(request.referrer)
|
104
104
|
|
105
|
-
|
105
|
+
hide_matching = (
|
106
|
+
literal_eval(request.args.get("hide_matching"))
|
107
|
+
if request.args.get("hide_matching")
|
108
|
+
else True
|
109
|
+
)
|
110
|
+
data = controllers.case(store, institute_obj, case_obj, hide_matching)
|
106
111
|
|
107
112
|
return dict(
|
108
113
|
**data,
|
@@ -271,10 +276,7 @@ def pdf_case_report(institute_id, case_name):
|
|
271
276
|
store=store, institute_obj=institute_obj, case_obj=case_obj
|
272
277
|
)
|
273
278
|
# add coverage report on the bottom of this report
|
274
|
-
if (
|
275
|
-
current_app.config.get("SQLALCHEMY_DATABASE_URI")
|
276
|
-
and case_obj.get("track", "rare") != "cancer"
|
277
|
-
):
|
279
|
+
if current_app.config.get("chanjo_report") and case_obj.get("track", "rare") != "cancer":
|
278
280
|
data["coverage_report"] = controllers.coverage_report_contents(
|
279
281
|
request.url_root, institute_obj, case_obj
|
280
282
|
)
|
@@ -28,7 +28,7 @@ from scout.server.blueprints.variant.utils import (
|
|
28
28
|
update_variant_case_panels,
|
29
29
|
)
|
30
30
|
from scout.server.blueprints.variants.utils import update_case_panels
|
31
|
-
from scout.server.extensions import LoqusDB,
|
31
|
+
from scout.server.extensions import LoqusDB, config_igv_tracks, gens
|
32
32
|
from scout.server.links import disease_link, get_variant_links
|
33
33
|
from scout.server.utils import (
|
34
34
|
case_has_alignments,
|
@@ -142,9 +142,9 @@ def get_igv_tracks(build: str = "37") -> set:
|
|
142
142
|
# Collect hardcoded tracks, common for all Scout instances
|
143
143
|
for track in IGV_TRACKS.get(build, []):
|
144
144
|
igv_tracks.add(track.get("name"))
|
145
|
-
# Collect instance-
|
146
|
-
if hasattr(
|
147
|
-
for track in
|
145
|
+
# Collect instance-specific public tracks, if available
|
146
|
+
if hasattr(config_igv_tracks, "tracks"):
|
147
|
+
for track in config_igv_tracks.tracks.get(build, []):
|
148
148
|
igv_tracks.add(track.get("name"))
|
149
149
|
return igv_tracks
|
150
150
|
|
@@ -372,7 +372,7 @@ def variant(
|
|
372
372
|
"ACMG_OPTIONS": ACMG_OPTIONS,
|
373
373
|
"case_tag_options": CASE_TAGS,
|
374
374
|
"inherit_palette": INHERITANCE_PALETTE,
|
375
|
-
"igv_tracks": get_igv_tracks(genome_build),
|
375
|
+
"igv_tracks": get_igv_tracks("38" if variant_obj["is_mitochondrial"] else genome_build),
|
376
376
|
"has_rna_tracks": case_has_rna_tracks(case_obj),
|
377
377
|
"gens_info": gens.connection_settings(genome_build),
|
378
378
|
"evaluations": evaluations,
|
@@ -29,22 +29,27 @@
|
|
29
29
|
<form action="{{ url_for('variant.variant_acmg', institute_id=institute._id, case_name=case.display_name, variant_id=variant._id) }}" method="POST">
|
30
30
|
<div class="card panel-default mt-3">
|
31
31
|
<div class="card-body">
|
32
|
-
{% if
|
33
|
-
|
34
|
-
<div class="text-center">
|
35
|
-
{% for option in ACMG_OPTIONS %}
|
36
|
-
<a id="acmg-{{ option.code }}" class="btn acmg-preview">{{ option.label }}</a>
|
37
|
-
{% endfor %}
|
38
|
-
</div>
|
39
|
-
</div>
|
40
|
-
<button class="btn btn-primary form-control">Submit</button>
|
41
|
-
{% else %}
|
42
|
-
<h4>
|
32
|
+
{% if evaluation %}
|
33
|
+
<h4>
|
43
34
|
{{ evaluation.classification.label }}
|
44
35
|
<span class="badge bg-info">{{ evaluation.classification.short }}</span>
|
45
36
|
</h4>
|
46
37
|
By {{ evaluation.user_name }} on {{ evaluation.created_at.date() }}
|
38
|
+
{% if edit %}
|
39
|
+
<br><br>
|
40
|
+
<button class="btn btn-primary form-control" data-bs-toggle="tooltip" title="Editing this classification will result in a new classification">Reclassify</button>
|
41
|
+
{% endif %}
|
42
|
+
{% elif not evaluation %}
|
43
|
+
<button class="btn btn-primary form-control">Submit</button>
|
47
44
|
{% endif %}
|
45
|
+
<!-- classification preview in the footer-->
|
46
|
+
<div class="mt-3 fixed-bottom bg-light border">
|
47
|
+
<div class="text-center">
|
48
|
+
{% for option in ACMG_OPTIONS %}
|
49
|
+
<a id="acmg-{{ option.code }}" class="btn acmg-preview">{{ option.label }}</a>
|
50
|
+
{% endfor %}
|
51
|
+
</div>
|
52
|
+
</div>
|
48
53
|
</div>
|
49
54
|
</div>
|
50
55
|
|
@@ -72,14 +77,14 @@
|
|
72
77
|
{{ criterion.short }}
|
73
78
|
</div>
|
74
79
|
<div class="form-check form-switch">
|
75
|
-
<input type="checkbox" class="form-check-input" id="checkbox-{{ criterion_code }}" name="criteria" value="{{ criterion_code }}" {{ 'checked' if evaluation and criterion_code in evaluation.criteria }} {{ 'disabled' if evaluation }}>
|
80
|
+
<input type="checkbox" class="form-check-input" id="checkbox-{{ criterion_code }}" name="criteria" value="{{ criterion_code }}" {{ 'checked' if evaluation and criterion_code in evaluation.criteria }} {{ 'disabled' if evaluation and edit is false}}>
|
76
81
|
<label class="form-check-label" for="checkbox-{{ criterion_code }}"></label>
|
77
82
|
</div>
|
78
83
|
</div>
|
79
84
|
<div id="comment-{{ criterion_code }}" class="{{ 'collapse' if not (comment or link or modifier) }} mt-2">
|
80
85
|
{% if criterion.documentation %}<div class="row"><span class="me-1 fw-light text-wrap">{{ criterion.documentation | safe}}</span></div>{% endif %}
|
81
86
|
<div class="row">
|
82
|
-
<select {{ 'disabled' if evaluation }} id="modifier-{{ criterion_code }}" name="modifier-{{ criterion_code }}" class="form-control form-select">
|
87
|
+
<select {{ 'disabled' if evaluation and edit is false}} id="modifier-{{ criterion_code }}" name="modifier-{{ criterion_code }}" class="form-control form-select">
|
83
88
|
<option value="" {% if not modifier %}selected{% endif %}>Strength modifier...</option>
|
84
89
|
{% for level in "Strong", "Moderate", "Supporting" %}
|
85
90
|
{% if(level != evidence) %}
|
@@ -89,10 +94,10 @@
|
|
89
94
|
</select>
|
90
95
|
</div>
|
91
96
|
<div class="row">
|
92
|
-
<textarea {{ 'disabled' if evaluation }} class="form-control"
|
97
|
+
<textarea {{ 'disabled' if evaluation and edit is false }} class="form-control"
|
93
98
|
name="comment-{{ criterion_code }}" rows="3" placeholder="Comment (optional)">{{ comment }}</textarea>
|
94
99
|
</div>
|
95
|
-
<input type="url" {{ 'disabled' if evaluation }} class="form-control" name="link-{{ criterion_code }}"
|
100
|
+
<input type="url" {{ 'disabled' if evaluation and edit is false }} class="form-control" name="link-{{ criterion_code }}"
|
96
101
|
placeholder="{{ link or 'Supporting link (optional)' }}" value="">
|
97
102
|
</div>
|
98
103
|
</div>
|
@@ -120,6 +125,11 @@
|
|
120
125
|
{{ super() }}
|
121
126
|
|
122
127
|
<script>
|
128
|
+
|
129
|
+
window.onload=function() {
|
130
|
+
update_classification();
|
131
|
+
}
|
132
|
+
|
123
133
|
$(function () {
|
124
134
|
$('[data-bs-toggle="tooltip"]').tooltip();
|
125
135
|
|
@@ -130,7 +140,6 @@
|
|
130
140
|
} else {
|
131
141
|
el.collapse('hide');
|
132
142
|
}
|
133
|
-
|
134
143
|
update_classification()
|
135
144
|
});
|
136
145
|
|
@@ -49,14 +49,19 @@
|
|
49
49
|
{% endif %}
|
50
50
|
</span>
|
51
51
|
</div>
|
52
|
-
<
|
53
|
-
<
|
52
|
+
<span>
|
53
|
+
<small class="text-muted">
|
54
54
|
{{ data.user_name }} on {{ data.created_at.date() }}
|
55
55
|
{% if current_variant %}
|
56
|
-
|
56
|
+
<form action="{{ url_for('variant.evaluation', evaluation_id=data._id) }}" method="POST" style="display: inline-block;">
|
57
|
+
<button class="btn btn-xs btn-link" >Delete</button>
|
58
|
+
</form>
|
59
|
+
{% if data.criteria %}
|
60
|
+
<a class="btn btn-xs btn-link" href="{{ url_for('variant.evaluation', evaluation_id=data._id, edit=True) }}" data-bs-toggle="tooltip" title="Editing this classification will result in a new classification">Edit</a>
|
61
|
+
{% endif %}
|
57
62
|
{% endif %}
|
58
|
-
</
|
59
|
-
</
|
63
|
+
</small>
|
64
|
+
</span>
|
60
65
|
</li>
|
61
66
|
{% endmacro %}
|
62
67
|
|
@@ -90,7 +95,7 @@
|
|
90
95
|
{% endif %}
|
91
96
|
</div>
|
92
97
|
{% endfor %}
|
93
|
-
{% if config.
|
98
|
+
{% if config.chanjo_report %}
|
94
99
|
<div class="d-flex flex-wrap ms-1">
|
95
100
|
{% for gene in variant.genes %}
|
96
101
|
<a class="btn btn-sm btn-secondary text-white" rel="noopener noreferrer" target="_blank" href="{{ url_for('report.gene', gene_id=gene.hgnc_id, sample_id=variant.samples|map(attribute='sample_id')|list) }}">
|
@@ -13,7 +13,7 @@ from flask import (
|
|
13
13
|
from flask_login import current_user
|
14
14
|
from markupsafe import Markup
|
15
15
|
|
16
|
-
from scout.constants import ACMG_CRITERIA, ACMG_MAP
|
16
|
+
from scout.constants import ACMG_CRITERIA, ACMG_MAP, ACMG_OPTIONS
|
17
17
|
from scout.server.blueprints.variant.controllers import check_reset_variant_classification
|
18
18
|
from scout.server.blueprints.variant.controllers import evaluation as evaluation_controller
|
19
19
|
from scout.server.blueprints.variant.controllers import observations, str_variant_reviewer
|
@@ -308,7 +308,7 @@ def variant_update(institute_id, case_name, variant_id):
|
|
308
308
|
@variant_bp.route("/evaluations/<evaluation_id>", methods=["GET", "POST"])
|
309
309
|
@templated("variant/acmg.html")
|
310
310
|
def evaluation(evaluation_id):
|
311
|
-
"""Show or delete an ACMG evaluation."""
|
311
|
+
"""Show, edit or delete an ACMG evaluation."""
|
312
312
|
|
313
313
|
evaluation_obj = store.get_evaluation(evaluation_id)
|
314
314
|
if evaluation_obj is None:
|
@@ -328,12 +328,15 @@ def evaluation(evaluation_id):
|
|
328
328
|
flash("Cleared ACMG classification.", "info")
|
329
329
|
|
330
330
|
return redirect(link)
|
331
|
+
|
331
332
|
return dict(
|
332
333
|
evaluation=evaluation_obj,
|
334
|
+
edit=bool(request.args.get("edit")),
|
333
335
|
institute=evaluation_obj["institute"],
|
334
336
|
case=evaluation_obj["case"],
|
335
337
|
variant=evaluation_obj["variant"],
|
336
338
|
CRITERIA=ACMG_CRITERIA,
|
339
|
+
ACMG_OPTIONS=ACMG_OPTIONS,
|
337
340
|
)
|
338
341
|
|
339
342
|
|
@@ -1996,7 +1996,7 @@ def activate_case(store, institute_obj, case_obj, current_user):
|
|
1996
1996
|
store.update_status(institute_obj, case_obj, user_obj, "active", case_link)
|
1997
1997
|
|
1998
1998
|
|
1999
|
-
def
|
1999
|
+
def reset_all_dismissed(store, institute_obj, case_obj):
|
2000
2000
|
"""Reset all dismissed variants for a case.
|
2001
2001
|
|
2002
2002
|
Args:
|
@@ -47,7 +47,7 @@ variants_bp = Blueprint(
|
|
47
47
|
def reset_dismissed(institute_id, case_name):
|
48
48
|
"""Reset all dismissed variants for a case"""
|
49
49
|
institute_obj, case_obj = institute_and_case(store, institute_id, case_name)
|
50
|
-
controllers.
|
50
|
+
controllers.reset_all_dismissed(store, institute_obj, case_obj)
|
51
51
|
return redirect(request.referrer)
|
52
52
|
|
53
53
|
|