scout-browser 4.101.0__py3-none-any.whl → 4.103.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/case.py +26 -122
- scout/adapter/mongo/clinvar.py +98 -32
- scout/adapter/mongo/event.py +0 -47
- scout/adapter/mongo/hgnc.py +7 -2
- scout/adapter/mongo/omics_variant.py +8 -0
- scout/adapter/mongo/variant_loader.py +12 -4
- scout/build/variant/variant.py +1 -0
- scout/commands/load/variants.py +1 -1
- scout/commands/update/user.py +87 -49
- scout/constants/__init__.py +4 -0
- scout/constants/clinvar.py +10 -0
- scout/constants/igv_tracks.py +6 -2
- scout/constants/phenotype.py +1 -0
- scout/constants/variant_tags.py +18 -0
- scout/demo/NIST.trgt.stranger.vcf.gz +0 -0
- scout/demo/NIST.trgt.stranger.vcf.gz.tbi +0 -0
- scout/demo/__init__.py +1 -0
- scout/load/hpo.py +8 -2
- scout/models/clinvar.py +86 -0
- scout/parse/variant/coordinates.py +5 -1
- scout/parse/variant/gene.py +5 -9
- scout/parse/variant/genotype.py +66 -42
- scout/parse/variant/variant.py +2 -0
- scout/server/app.py +71 -2
- scout/server/blueprints/alignviewers/controllers.py +8 -6
- scout/server/blueprints/alignviewers/templates/alignviewers/igv_viewer.html +4 -0
- scout/server/blueprints/alignviewers/templates/alignviewers/utils.html +1 -1
- scout/server/blueprints/cases/controllers.py +57 -29
- scout/server/blueprints/cases/templates/cases/case_report.html +28 -90
- scout/server/blueprints/cases/templates/cases/matchmaker.html +1 -1
- scout/server/blueprints/cases/templates/cases/phenotype.html +1 -1
- scout/server/blueprints/cases/templates/cases/utils.html +34 -53
- scout/server/blueprints/cases/views.py +32 -33
- scout/server/blueprints/clinvar/controllers.py +235 -54
- scout/server/blueprints/clinvar/form.py +38 -1
- scout/server/blueprints/clinvar/static/form_style.css +8 -1
- scout/server/blueprints/clinvar/templates/clinvar/clinvar_onc_submissions.html +200 -0
- scout/server/blueprints/clinvar/templates/clinvar/clinvar_submissions.html +3 -2
- scout/server/blueprints/clinvar/templates/clinvar/components.html +198 -0
- scout/server/blueprints/clinvar/templates/clinvar/multistep_add_onc_variant.html +187 -0
- scout/server/blueprints/clinvar/templates/clinvar/multistep_add_variant.html +9 -348
- scout/server/blueprints/clinvar/templates/clinvar/scripts.html +193 -0
- scout/server/blueprints/clinvar/views.py +90 -13
- scout/server/blueprints/diagnoses/controllers.py +4 -8
- scout/server/blueprints/diagnoses/templates/diagnoses/diagnoses.html +1 -1
- scout/server/blueprints/diagnoses/templates/diagnoses/disease_term.html +1 -1
- scout/server/blueprints/diagnoses/views.py +2 -2
- scout/server/blueprints/institutes/controllers.py +148 -75
- scout/server/blueprints/institutes/forms.py +1 -0
- scout/server/blueprints/institutes/templates/overview/cases.html +1 -1
- scout/server/blueprints/institutes/templates/overview/gene_variants.html +15 -6
- scout/server/blueprints/institutes/templates/overview/institute_sidebar.html +28 -2
- scout/server/blueprints/institutes/templates/overview/utils.html +1 -1
- scout/server/blueprints/institutes/views.py +17 -4
- scout/server/blueprints/login/controllers.py +2 -1
- scout/server/blueprints/login/views.py +5 -2
- scout/server/blueprints/mme/templates/mme/mme_submissions.html +2 -2
- scout/server/blueprints/omics_variants/templates/omics_variants/outliers.html +2 -2
- scout/server/blueprints/omics_variants/views.py +2 -2
- scout/server/blueprints/phenotypes/controllers.py +15 -2
- scout/server/blueprints/phenotypes/templates/phenotypes/hpo_terms.html +1 -1
- scout/server/blueprints/variant/controllers.py +11 -12
- scout/server/blueprints/variant/templates/variant/cancer-variant.html +2 -1
- scout/server/blueprints/variant/templates/variant/components.html +0 -1
- scout/server/blueprints/variant/templates/variant/sv-variant.html +2 -1
- scout/server/blueprints/variant/templates/variant/utils.html +1 -1
- scout/server/blueprints/variant/templates/variant/variant.html +2 -2
- scout/server/blueprints/variant/templates/variant/variant_details.html +100 -84
- scout/server/blueprints/variant/utils.py +25 -0
- scout/server/blueprints/variants/controllers.py +11 -42
- scout/server/blueprints/variants/templates/variants/cancer-variants.html +5 -3
- scout/server/blueprints/variants/templates/variants/str-variants.html +4 -1
- scout/server/blueprints/variants/templates/variants/sv-variants.html +3 -3
- scout/server/blueprints/variants/templates/variants/utils.html +4 -0
- scout/server/blueprints/variants/templates/variants/variants.html +4 -4
- scout/server/blueprints/variants/views.py +9 -8
- scout/server/config.py +3 -0
- scout/server/extensions/beacon_extension.py +7 -2
- scout/server/extensions/clinvar_extension.py +2 -2
- scout/server/templates/bootstrap_global.html +11 -1
- scout/server/templates/layout.html +6 -1
- scout/server/utils.py +24 -3
- {scout_browser-4.101.0.dist-info → scout_browser-4.103.0.dist-info}/METADATA +1 -1
- {scout_browser-4.101.0.dist-info → scout_browser-4.103.0.dist-info}/RECORD +87 -81
- {scout_browser-4.101.0.dist-info → scout_browser-4.103.0.dist-info}/WHEEL +0 -0
- {scout_browser-4.101.0.dist-info → scout_browser-4.103.0.dist-info}/entry_points.txt +0 -0
- {scout_browser-4.101.0.dist-info → scout_browser-4.103.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,10 +1,11 @@
|
|
1
1
|
import logging
|
2
2
|
from json import dumps
|
3
3
|
from tempfile import NamedTemporaryFile
|
4
|
-
from typing import List, Tuple
|
4
|
+
from typing import List, Optional, Tuple
|
5
5
|
|
6
6
|
from flask import (
|
7
7
|
Blueprint,
|
8
|
+
Response,
|
8
9
|
flash,
|
9
10
|
redirect,
|
10
11
|
render_template,
|
@@ -18,9 +19,10 @@ from scout.constants.clinvar import (
|
|
18
19
|
CASEDATA_HEADER,
|
19
20
|
CLINVAR_HEADER,
|
20
21
|
GERMLINE_CLASSIF_TERMS,
|
22
|
+
ONCOGENIC_CLASSIF_TERMS,
|
21
23
|
)
|
22
24
|
from scout.server.extensions import clinvar_api, store
|
23
|
-
from scout.server.utils import institute_and_case
|
25
|
+
from scout.server.utils import institute_and_case, safe_redirect_back
|
24
26
|
|
25
27
|
from . import controllers
|
26
28
|
|
@@ -40,8 +42,13 @@ def clinvar_submission_status(submission_id):
|
|
40
42
|
"""Sends a request to ClinVar to retrieve and display the status of a submission."""
|
41
43
|
|
42
44
|
# flash a message with current submission status for a ClinVar submission
|
45
|
+
clinvar_resp_status = dict(
|
46
|
+
clinvar_api.json_submission_status(
|
47
|
+
submission_id=submission_id, api_key=request.form.get("apiKey")
|
48
|
+
)
|
49
|
+
)
|
43
50
|
flash(
|
44
|
-
f
|
51
|
+
f"Response from ClinVar: {clinvar_resp_status}",
|
45
52
|
"primary",
|
46
53
|
)
|
47
54
|
return redirect(request.referrer)
|
@@ -76,8 +83,8 @@ def clinvar_add_variant(institute_id, case_name):
|
|
76
83
|
|
77
84
|
|
78
85
|
@clinvar_bp.route("/<institute_id>/<case_name>/clinvar/save", methods=["POST"])
|
79
|
-
def clinvar_save(institute_id, case_name):
|
80
|
-
"""Adds one variant with eventual CaseData observations to an open (or new) ClinVar submission"""
|
86
|
+
def clinvar_save(institute_id: str, case_name: str):
|
87
|
+
"""Adds one germline variant with eventual CaseData observations to an open (or new) ClinVar submission."""
|
81
88
|
institute_obj, case_obj = institute_and_case(store, institute_id, case_name)
|
82
89
|
controllers.add_variant_to_submission(
|
83
90
|
institute_obj=institute_obj, case_obj=case_obj, form=request.form
|
@@ -85,14 +92,14 @@ def clinvar_save(institute_id, case_name):
|
|
85
92
|
return redirect(url_for("cases.case", institute_id=institute_id, case_name=case_name))
|
86
93
|
|
87
94
|
|
88
|
-
@clinvar_bp.route("/<institute_id>/
|
89
|
-
def
|
95
|
+
@clinvar_bp.route("/<institute_id>/clinvar_germline_submissions", methods=["GET"])
|
96
|
+
def clinvar_germline_submissions(institute_id):
|
90
97
|
"""Handle clinVar submission objects and files"""
|
91
98
|
|
92
99
|
institute_obj = institute_and_case(store, institute_id)
|
93
100
|
institute_clinvar_submitters: List[str] = institute_obj.get("clinvar_submitters", [])
|
94
101
|
data = {
|
95
|
-
"submissions": store.
|
102
|
+
"submissions": store.get_clinvar_germline_submissions(institute_id),
|
96
103
|
"institute": institute_obj,
|
97
104
|
"variant_header_fields": CLINVAR_HEADER,
|
98
105
|
"casedata_header_fields": CASEDATA_HEADER,
|
@@ -131,24 +138,94 @@ def clinvar_update_submission(institute_id, submission):
|
|
131
138
|
|
132
139
|
|
133
140
|
@clinvar_bp.route("/<submission>/download/json/<clinvar_id>", methods=["GET"])
|
134
|
-
def clinvar_download_json(submission, clinvar_id):
|
135
|
-
"""Download a json for a clinVar submission
|
141
|
+
def clinvar_download_json(submission: str, clinvar_id: Optional[str]) -> Response:
|
142
|
+
"""Download a json for a clinVar submission.
|
143
|
+
|
144
|
+
Accepts:
|
145
|
+
submission:. It's the _id of the submission in the database
|
146
|
+
clinvar_id: It's the submission number (i.e. SUB123456). Might be available or not in the submission dictionary
|
147
|
+
|
148
|
+
"""
|
149
|
+
filename = clinvar_id if clinvar_id != "None" else submission
|
136
150
|
|
137
151
|
code, conversion_res = controllers.json_api_submission(submission_id=submission)
|
138
152
|
|
139
153
|
if code in [200, 201]:
|
140
154
|
# Write temp CSV file and serve it in response
|
141
|
-
tmp_json = NamedTemporaryFile(mode="a+", prefix=
|
155
|
+
tmp_json = NamedTemporaryFile(mode="a+", prefix=filename, suffix=".json")
|
142
156
|
tmp_json.write(dumps(conversion_res, indent=4))
|
143
157
|
|
144
158
|
tmp_json.flush()
|
145
159
|
tmp_json.seek(0)
|
146
160
|
return send_file(
|
147
161
|
tmp_json.name,
|
148
|
-
download_name=f"{
|
162
|
+
download_name=f"{filename}.json",
|
149
163
|
mimetype="application/json",
|
150
164
|
as_attachment=True,
|
151
165
|
)
|
152
166
|
else:
|
153
|
-
flash(
|
167
|
+
flash(
|
168
|
+
f"JSON file could not be crated for ClinVar submission: {filename}: {conversion_res}",
|
169
|
+
"warning",
|
170
|
+
)
|
154
171
|
return redirect(request.referrer)
|
172
|
+
|
173
|
+
|
174
|
+
### ClinVar oncogenicity variants submissions views
|
175
|
+
|
176
|
+
|
177
|
+
@clinvar_bp.route("/<institute_id>/clinvar_onc_submissions", methods=["GET"])
|
178
|
+
def clinvar_onc_submissions(institute_id):
|
179
|
+
"""Handle clinVar submission objects and files"""
|
180
|
+
|
181
|
+
institute_obj = institute_and_case(store, institute_id)
|
182
|
+
institute_clinvar_submitters: List[str] = institute_obj.get("clinvar_submitters", [])
|
183
|
+
data = {
|
184
|
+
"submissions": list(store.get_clinvar_onc_submissions(institute_id)),
|
185
|
+
"institute": institute_obj,
|
186
|
+
"show_submit": current_user.email in institute_clinvar_submitters
|
187
|
+
or not institute_clinvar_submitters,
|
188
|
+
}
|
189
|
+
return render_template("clinvar/clinvar_onc_submissions.html", **data)
|
190
|
+
|
191
|
+
|
192
|
+
@clinvar_bp.route("/<institute_id>/<case_name>/clinvar/clinvar_add_onc_variant", methods=["POST"])
|
193
|
+
def clinvar_add_onc_variant(institute_id: str, case_name: str):
|
194
|
+
"""Create a ClinVar submission document in database for one or more variants from a case."""
|
195
|
+
institute_obj, case_obj = institute_and_case(store, institute_id, case_name)
|
196
|
+
data = {
|
197
|
+
"institute": institute_obj,
|
198
|
+
"case": case_obj,
|
199
|
+
"onc_classif_terms": ONCOGENIC_CLASSIF_TERMS,
|
200
|
+
}
|
201
|
+
controllers.set_onc_clinvar_form(request.form.get("var_id"), data)
|
202
|
+
return render_template("clinvar/multistep_add_onc_variant.html", **data)
|
203
|
+
|
204
|
+
|
205
|
+
@clinvar_bp.route(
|
206
|
+
"/<institute_id>/<case_name>/clinvar_onc/clinvar_save_onc_variant", methods=["POST"]
|
207
|
+
)
|
208
|
+
def clinvar_onc_save(institute_id: str, case_name: str):
|
209
|
+
"""Adds one somatic variant with eventual CaseData observations to an open (or new) ClinVar congenicity submission"""
|
210
|
+
institute_obj, case_obj = institute_and_case(store, institute_id, case_name)
|
211
|
+
controllers.add_onc_variant_to_submission(
|
212
|
+
institute_obj=institute_obj, case_obj=case_obj, form=request.form
|
213
|
+
)
|
214
|
+
return redirect(url_for("cases.case", institute_id=institute_id, case_name=case_name))
|
215
|
+
|
216
|
+
|
217
|
+
@clinvar_bp.route("/<submission>/clinvar_onc/delete_variant", methods=["POST"])
|
218
|
+
def clinvar_delete_onc_variant(submission: str):
|
219
|
+
"""Delete a single variant (oncogenicitySubmission) from the ClinVar submissions collection."""
|
220
|
+
store.delete_clinvar_onc_var(
|
221
|
+
submission=submission,
|
222
|
+
variant_id=request.form.get("delete_object"),
|
223
|
+
)
|
224
|
+
return safe_redirect_back(request)
|
225
|
+
|
226
|
+
|
227
|
+
@clinvar_bp.route("/<submission>/download", methods=["GET"])
|
228
|
+
def clinvar_download(submission):
|
229
|
+
"""Download a json file for a clinVar submission. This function is only used for oncogenocity submissions for the time being"""
|
230
|
+
|
231
|
+
return store.get_onc_submission_json(submission)
|
@@ -1,16 +1,13 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
|
3
3
|
from scout.adapter import MongoAdapter
|
4
|
+
from scout.constants import HPO_LINK_URL
|
4
5
|
from scout.server.links import disease_link
|
5
6
|
|
6
7
|
|
7
|
-
def disease_entry(store, disease_id) -> dict:
|
8
|
+
def disease_entry(store: MongoAdapter, disease_id: str) -> dict:
|
8
9
|
"""Retrieve specific info for a disease term
|
9
10
|
|
10
|
-
Args:
|
11
|
-
store(obj): an adapter to the scout database
|
12
|
-
disease_id(str): a disease_id
|
13
|
-
|
14
11
|
Returns:
|
15
12
|
disease_obj(obj): a disease term containing description and genes
|
16
13
|
"""
|
@@ -21,6 +18,7 @@ def disease_entry(store, disease_id) -> dict:
|
|
21
18
|
store.hpo_term(hpo_id) for hpo_id in disease_obj.get("hpo_terms", [])
|
22
19
|
]
|
23
20
|
disease_obj["disease_link"] = disease_link(disease_id=disease_obj["disease_id"])
|
21
|
+
disease_obj["hpo_link_url"] = HPO_LINK_URL
|
24
22
|
return disease_obj
|
25
23
|
|
26
24
|
|
@@ -43,6 +41,4 @@ def disease_terms(store: MongoAdapter, query: str, source: str) -> dict:
|
|
43
41
|
)
|
44
42
|
disease.update({"genes": gene_ids_symbols})
|
45
43
|
|
46
|
-
|
47
|
-
|
48
|
-
return data
|
44
|
+
return {"terms": list(disease_data)}
|
@@ -95,7 +95,7 @@
|
|
95
95
|
</td>
|
96
96
|
<td id="hpo-container">
|
97
97
|
<span class="text-body">
|
98
|
-
<a id="hpo-link" class="badge bg-secondary text-white m-1" href="
|
98
|
+
<a id="hpo-link" class="badge bg-secondary text-white m-1" href="{{hpo_link_url}}{{term}}" target="_blank" rel="noopener" ></a>
|
99
99
|
</span>
|
100
100
|
</td>
|
101
101
|
</tr>
|
@@ -47,7 +47,7 @@
|
|
47
47
|
<ul class="list-group list-group-flush">
|
48
48
|
{% for pheno in hpo_complete %}
|
49
49
|
<li class="list-group-item">
|
50
|
-
<a href="
|
50
|
+
<a href="{{hpo_link_url}}{{pheno.hpo_id}}" target="_blank">{{ pheno.hpo_id }}</a>
|
51
51
|
{{pheno.description}}
|
52
52
|
</li>
|
53
53
|
{% else %}
|
@@ -2,6 +2,7 @@ from typing import Union
|
|
2
2
|
|
3
3
|
from flask import Blueprint, jsonify, request
|
4
4
|
|
5
|
+
from scout.constants import HPO_LINK_URL
|
5
6
|
from scout.server.extensions import store
|
6
7
|
from scout.server.utils import public_endpoint, templated
|
7
8
|
|
@@ -30,8 +31,7 @@ def diagnosis(disease_id):
|
|
30
31
|
def count_diagnoses():
|
31
32
|
"""Display the diagnosis counts for each coding system available in database"""
|
32
33
|
|
33
|
-
|
34
|
-
return data
|
34
|
+
return {"counts": store.disease_terminology_count(), "hpo_link_url": HPO_LINK_URL}
|
35
35
|
|
36
36
|
|
37
37
|
@omim_bp.route("/api/v1/diagnoses")
|
@@ -25,7 +25,11 @@ from scout.server.blueprints.variant.utils import (
|
|
25
25
|
update_representative_gene,
|
26
26
|
)
|
27
27
|
from scout.server.extensions import beacon, store
|
28
|
-
from scout.server.utils import
|
28
|
+
from scout.server.utils import (
|
29
|
+
get_case_genome_build,
|
30
|
+
institute_and_case,
|
31
|
+
user_institutes,
|
32
|
+
)
|
29
33
|
|
30
34
|
from .forms import BeaconDatasetForm, CaseFilterForm
|
31
35
|
|
@@ -47,6 +51,33 @@ VAR_SPECIFIC_EVENTS = [
|
|
47
51
|
"cancel_sanger",
|
48
52
|
]
|
49
53
|
|
54
|
+
# Query terms in default, non-specific queries for cases
|
55
|
+
NONSPECIFIC_QUERY_TERMS = [
|
56
|
+
"collaborators",
|
57
|
+
]
|
58
|
+
|
59
|
+
# Projection for fetching cases
|
60
|
+
ALL_CASES_PROJECTION = {
|
61
|
+
"analysis_date": 1,
|
62
|
+
"assignees": 1,
|
63
|
+
"beacon": 1,
|
64
|
+
"case_id": 1,
|
65
|
+
"display_name": 1,
|
66
|
+
"genome_build": 1,
|
67
|
+
"individuals": 1,
|
68
|
+
"is_rerun": 1,
|
69
|
+
"is_research": 1,
|
70
|
+
"mme_submission": 1,
|
71
|
+
"owner": 1,
|
72
|
+
"panels": 1,
|
73
|
+
"phenotype_terms": 1,
|
74
|
+
"rank_model_version": 1,
|
75
|
+
"status": 1,
|
76
|
+
"sv_rank_model_version": 1,
|
77
|
+
"track": 1,
|
78
|
+
"vcf_files": 1,
|
79
|
+
}
|
80
|
+
|
50
81
|
|
51
82
|
def get_timeline_data(limit):
|
52
83
|
"""Retrieve chronologially ordered events from the database to display them in the timeline page
|
@@ -519,92 +550,136 @@ def export_case_samples(institute_id, filtered_cases) -> Response:
|
|
519
550
|
)
|
520
551
|
|
521
552
|
|
522
|
-
def
|
523
|
-
|
524
|
-
|
553
|
+
def get_cases_by_query(
|
554
|
+
store: MongoAdapter,
|
555
|
+
request: request,
|
556
|
+
institute_id: str,
|
557
|
+
) -> list:
|
558
|
+
"""Fetch additional cases based on filters given in request query form.
|
525
559
|
|
526
|
-
|
527
|
-
|
528
|
-
data["institute"] = institute_obj
|
529
|
-
data["form"] = CaseFilterForm(request.form)
|
530
|
-
data["status_ncases"] = store.nr_cases_by_status(institute_id=institute_id)
|
531
|
-
data["nr_cases"] = sum(data["status_ncases"].values())
|
560
|
+
Sets metadata in data, e.g. sort order.
|
561
|
+
"""
|
532
562
|
|
533
|
-
|
534
|
-
|
535
|
-
|
563
|
+
name_query = request.form
|
564
|
+
all_cases = store.cases(
|
565
|
+
collaborator=institute_id,
|
566
|
+
name_query=name_query,
|
567
|
+
skip_assigned=request.form.get("skip_assigned"),
|
568
|
+
is_research=request.form.get("is_research"),
|
569
|
+
has_rna_data=request.form.get("has_rna"),
|
570
|
+
verification_pending=request.form.get("validation_ordered"),
|
571
|
+
has_clinvar_submission=request.form.get("clinvar_submitted"),
|
572
|
+
projection=ALL_CASES_PROJECTION,
|
573
|
+
)
|
536
574
|
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
"sv_rank_model_version": 1,
|
555
|
-
"track": 1,
|
556
|
-
"vcf_files": 1,
|
557
|
-
}
|
575
|
+
return all_cases
|
576
|
+
|
577
|
+
|
578
|
+
def get_and_set_cases_by_status(
|
579
|
+
store: MongoAdapter,
|
580
|
+
request: request,
|
581
|
+
institute_obj: dict,
|
582
|
+
previous_query_result_cases: list,
|
583
|
+
data: dict,
|
584
|
+
) -> dict:
|
585
|
+
"""Process cases for statuses that require all cases to be shown.
|
586
|
+
Group cases by status, process additional cases for the remaining statuses
|
587
|
+
and ensure that we don't dim cases that already appeared in query search results.
|
588
|
+
"""
|
589
|
+
|
590
|
+
status_show_all_cases = institute_obj.get("show_all_cases_status", ["prioritized"])
|
591
|
+
nr_cases_showall_statuses = 0
|
558
592
|
|
559
593
|
# Group cases by status
|
560
594
|
case_groups = {status: [] for status in CASE_STATUSES}
|
561
|
-
nr_cases_showall_statuses = 0
|
562
|
-
status_show_all_cases = institute_obj.get("show_all_cases_status") or ["prioritized"]
|
563
595
|
|
564
|
-
# Process cases for statuses that require all cases to be shown
|
565
596
|
for status in status_show_all_cases:
|
566
597
|
cases_in_status = store.cases_by_status(
|
567
|
-
institute_id=
|
598
|
+
institute_id=institute_obj["_id"], status=status, projection=ALL_CASES_PROJECTION
|
568
599
|
)
|
569
600
|
cases_in_status = _sort_cases(data, request, cases_in_status)
|
570
601
|
for case_obj in cases_in_status:
|
571
602
|
populate_case_obj(case_obj, store)
|
603
|
+
case_obj["dimmed_in_search"] = True
|
572
604
|
case_groups[status].append(case_obj)
|
573
605
|
nr_cases_showall_statuses += 1
|
574
606
|
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
607
|
+
def get_specific_query(request: request) -> bool:
|
608
|
+
"""Check if only non-specific query terms were used in query,
|
609
|
+
by yielding the query without actually executing it.
|
610
|
+
|
611
|
+
If so we assume this is a default query, and dim all cases
|
612
|
+
that match the "show_all_cases_status", highlighting the
|
613
|
+
(max limit number) other cases that were returned.
|
614
|
+
|
615
|
+
If this is a specific query, cases returned by this query part will be explicitly
|
616
|
+
highlighted even if they have a status that matches "show_all_cases_status".
|
617
|
+
"""
|
618
|
+
cases_query: dict = store.cases(
|
619
|
+
collaborator=institute_obj["_id"],
|
620
|
+
name_query=request.form,
|
621
|
+
skip_assigned=request.form.get("skip_assigned"),
|
622
|
+
is_research=request.form.get("is_research"),
|
623
|
+
has_rna_data=request.form.get("has_rna"),
|
624
|
+
verification_pending=request.form.get("validation_ordered"),
|
625
|
+
has_clinvar_submission=request.form.get("clinvar_submitted"),
|
626
|
+
yield_query=True,
|
627
|
+
)
|
628
|
+
for key, value in cases_query.items():
|
629
|
+
if key not in NONSPECIFIC_QUERY_TERMS and value not in [None, ""]:
|
630
|
+
return True
|
631
|
+
return False
|
632
|
+
|
633
|
+
specific_query_asked = get_specific_query(request)
|
634
|
+
|
635
|
+
nr_name_query_matching_displayed_cases = 0
|
636
|
+
limit = int(request.form.get("search_limit", 100))
|
637
|
+
for case_obj in previous_query_result_cases:
|
638
|
+
if case_obj["status"] in status_show_all_cases:
|
639
|
+
if specific_query_asked:
|
640
|
+
for group_case in case_groups[status]:
|
641
|
+
if group_case["_id"] == case_obj["_id"]:
|
642
|
+
group_case["dimmed_in_search"] = False
|
643
|
+
elif nr_name_query_matching_displayed_cases == limit:
|
644
|
+
break
|
645
|
+
else:
|
646
|
+
populate_case_obj(case_obj, store)
|
647
|
+
case_groups[case_obj["status"]].append(case_obj)
|
648
|
+
nr_name_query_matching_displayed_cases += 1
|
649
|
+
|
650
|
+
data["found_cases"] = nr_name_query_matching_displayed_cases + nr_cases_showall_statuses
|
651
|
+
data["limit"] = limit
|
652
|
+
return case_groups
|
653
|
+
|
654
|
+
|
655
|
+
def cases(store: MongoAdapter, request: request, institute_id: str):
|
656
|
+
"""Preprocess case objects for the 'cases' view.
|
657
|
+
|
658
|
+
Returns data dict for view display, or response in case of file export.
|
659
|
+
"""
|
660
|
+
data = {}
|
661
|
+
|
662
|
+
# Initialize data (institute info, filters, and case counts)
|
663
|
+
institute_obj = institute_and_case(store, institute_id)
|
664
|
+
data["institute"] = institute_obj
|
665
|
+
data["form"] = CaseFilterForm(request.form)
|
666
|
+
data["status_ncases"] = store.nr_cases_by_status(institute_id=institute_id)
|
667
|
+
data["nr_cases"] = sum(data["status_ncases"].values())
|
668
|
+
|
669
|
+
# Fetch Sanger unevaluated and validated cases
|
670
|
+
sanger_ordered_not_validated = get_sanger_unevaluated(store, institute_id, current_user.email)
|
671
|
+
data["sanger_unevaluated"], data["sanger_validated_by_others"] = sanger_ordered_not_validated
|
672
|
+
|
673
|
+
all_cases = get_cases_by_query(store, request, institute_id)
|
588
674
|
all_cases = _sort_cases(data, request, all_cases)
|
589
675
|
|
590
676
|
if request.form.get("export"):
|
591
677
|
return export_case_samples(institute_id, all_cases)
|
592
678
|
|
593
|
-
|
594
|
-
nr_cases = 0
|
595
|
-
for case_obj in all_cases:
|
596
|
-
if case_obj["status"] not in status_show_all_cases:
|
597
|
-
if nr_cases == limit:
|
598
|
-
break
|
599
|
-
populate_case_obj(case_obj, store)
|
600
|
-
case_groups[case_obj["status"]].append(case_obj)
|
601
|
-
nr_cases += 1
|
679
|
+
case_groups = get_and_set_cases_by_status(store, request, institute_obj, all_cases, data)
|
602
680
|
|
603
681
|
# Compile the final data
|
604
682
|
data["cases"] = [(status, case_groups[status]) for status in CASE_STATUSES]
|
605
|
-
data["found_cases"] = nr_cases + nr_cases_showall_statuses
|
606
|
-
data["limit"] = limit
|
607
|
-
|
608
683
|
return data
|
609
684
|
|
610
685
|
|
@@ -615,7 +690,9 @@ def populate_case_obj(case_obj: dict, store: MongoAdapter):
|
|
615
690
|
analysis_types = set(["mixed"])
|
616
691
|
case_obj["analysis_types"] = list(analysis_types)
|
617
692
|
|
618
|
-
case_obj["assignees"] = [
|
693
|
+
case_obj["assignees"] = [
|
694
|
+
store.user(user_id=user_id) for user_id in case_obj.get("assignees", [])
|
695
|
+
]
|
619
696
|
|
620
697
|
last_analysis_date = case_obj.get("analysis_date", datetime.datetime.now())
|
621
698
|
all_analyses_dates = {
|
@@ -626,7 +703,7 @@ def populate_case_obj(case_obj: dict, store: MongoAdapter):
|
|
626
703
|
all_analyses_dates
|
627
704
|
)
|
628
705
|
|
629
|
-
case_obj["clinvar_variants"] = store.
|
706
|
+
case_obj["clinvar_variants"] = store.case_to_clinvars(case_obj["_id"])
|
630
707
|
case_obj["display_track"] = TRACKS.get(case_obj.get("track", "rare"))
|
631
708
|
|
632
709
|
|
@@ -788,7 +865,9 @@ def export_gene_variants(
|
|
788
865
|
) # CADD score
|
789
866
|
variant_line.append(" | ".join(variant.get("region_annotations", []))) # Region
|
790
867
|
variant_line.append(" | ".join(variant.get("functional_annotations", []))) # Function
|
791
|
-
variant_line.append(
|
868
|
+
variant_line.append(
|
869
|
+
" | ".join(current_app.custom_filters.format_variant_canonical_transcripts(variant))
|
870
|
+
)
|
792
871
|
|
793
872
|
export_lines.append(",".join(variant_line))
|
794
873
|
|
@@ -818,10 +897,12 @@ def gene_variants(store, pymongo_cursor, variant_count, page=1, per_page=50):
|
|
818
897
|
for variant_obj in variant_res:
|
819
898
|
# Populate variant case_display_name
|
820
899
|
variant_case_obj = store.case(case_id=variant_obj["case_id"])
|
900
|
+
if variant_case_obj is None:
|
901
|
+
continue
|
821
902
|
case_display_name = variant_case_obj.get("display_name")
|
822
903
|
variant_obj["case_display_name"] = case_display_name
|
823
904
|
|
824
|
-
genome_build =
|
905
|
+
genome_build = get_case_genome_build(variant_case_obj)
|
825
906
|
update_variant_genes(store, variant_obj, genome_build)
|
826
907
|
variants.append(variant_obj)
|
827
908
|
|
@@ -885,14 +966,6 @@ def update_variant_genes(store, variant_obj, genome_build):
|
|
885
966
|
variant_obj["functional_annotations"] = get_annotations(gene_symbols, functional_annotations)
|
886
967
|
|
887
968
|
|
888
|
-
def get_genome_build(variant_case_obj):
|
889
|
-
"""Find genom build in `variant_case_obj`. If not found use build #37"""
|
890
|
-
build = str(variant_case_obj.get("genome_build"))
|
891
|
-
if build in ["37", "38"]:
|
892
|
-
return build
|
893
|
-
return "37"
|
894
|
-
|
895
|
-
|
896
969
|
def get_hgvs(gene_obj: Dict) -> Tuple[str, str, str]:
|
897
970
|
"""Analyse gene object for hgvs info
|
898
971
|
Return:
|
@@ -152,6 +152,7 @@ class GeneVariantFiltersForm(FlaskForm):
|
|
152
152
|
"HGNC Symbols (comma-separated, case sensitive)",
|
153
153
|
validators=[validators.InputRequired()],
|
154
154
|
)
|
155
|
+
institute = SelectMultipleField(choices=[])
|
155
156
|
rank_score = IntegerField(default=15)
|
156
157
|
phenotype_terms = TagListField("HPO terms (comma-separated)")
|
157
158
|
phenotype_groups = TagListField("Phenotype groups")
|
@@ -63,7 +63,7 @@
|
|
63
63
|
|
64
64
|
|
65
65
|
{% macro case_row(case) %}
|
66
|
-
<tr class="{% if case.status == 'solved' %}causative{% endif %}">
|
66
|
+
<tr class="{% if case.status == 'solved' %}causative{% elif case.dimmed_in_search %}dismiss{% endif %}">
|
67
67
|
<td>
|
68
68
|
<a class="me-2"
|
69
69
|
{% if case.individuals|length == 1 %} data-bs-toggle="tooltip" title="{{case.individuals[0].display_name}}" {% endif %}
|
@@ -2,7 +2,7 @@
|
|
2
2
|
{% from "variants/components.html" import frequency_cell_general, variant_funct_anno_cell, variant_gene_symbols_cell %}
|
3
3
|
{% from "utils.html" import comments_table %}
|
4
4
|
{% from "overview/institute_sidebar.html" import institute_actionbar %}
|
5
|
-
{% from "variants/utils.html" import pagination_footer, pagination_hidden_div %}
|
5
|
+
{% from "variants/utils.html" import pagination_footer, pagination_hidden_div, variant_rank_score %}
|
6
6
|
|
7
7
|
{% block title %}
|
8
8
|
{{ super() }} - {{ institute.display_name }} - All SNVs and INDELs
|
@@ -96,17 +96,22 @@
|
|
96
96
|
{{ variant.sub_category|upper }}({{ variant.chromosome }}{{ variant.cytoband_start }}-{{ variant.end_chrom }}{{ variant.cytoband_end }})
|
97
97
|
{% endif %}
|
98
98
|
{% else %}
|
99
|
-
|
99
|
+
<a href="{{ url_for('variant.variant', institute_id=variant.institute,
|
100
100
|
case_name=variant.case_display_name, variant_id=variant._id) }}" target="_blank">
|
101
|
-
|
102
|
-
|
103
|
-
|
101
|
+
{% set lines = variant | format_variant_canonical_transcripts %}
|
102
|
+
{% if lines | length > 1 %}
|
103
|
+
{{ lines | join('<br>') | safe }}
|
104
|
+
{% elif lines %}
|
105
|
+
{{ lines[0] }}
|
106
|
+
{% endif %}
|
107
|
+
{% endif %}
|
108
|
+
</a>
|
104
109
|
{% endmacro %}
|
105
110
|
|
106
111
|
{% macro cell_rank(variant) %}
|
107
112
|
{{ variant.case_display_name }}
|
108
113
|
:
|
109
|
-
<span class="badge bg-info">{{ variant
|
114
|
+
<span class="badge bg-info">{{ variant_rank_score(variant) }}</span>
|
110
115
|
{% endmacro %}
|
111
116
|
|
112
117
|
{% macro cell_cadd(variant) %}
|
@@ -130,6 +135,10 @@
|
|
130
135
|
{{ form.hgnc_symbols.label(class="control-label") }}
|
131
136
|
{{ form.hgnc_symbols(class="form-control") }}
|
132
137
|
</div>
|
138
|
+
<div class="col col-md-2">
|
139
|
+
{{ form.institute.label(class="control-label") }}
|
140
|
+
{{ form.institute(class="form-control", class="selectpicker", data_style="btn-secondary") }}
|
141
|
+
</div>
|
133
142
|
<div class="col col-md-1">
|
134
143
|
<label class="control-label" for="rank_score">Rank Score</label>
|
135
144
|
<input type="number" class="form-control" id="rank_score" name="rank_score" min="5" value={{form.rank_score.data}}>
|
@@ -44,12 +44,38 @@
|
|
44
44
|
</a>
|
45
45
|
|
46
46
|
<!-- ClinVar data menu item -->
|
47
|
-
<a href="
|
47
|
+
<a href="#submenu1" aria-controls="submenu1" data-bs-toggle="collapse" aria-expanded="false" class="bg-dark list-group-item list-group-item-action flex-column align-items-start">
|
48
48
|
<div class="d-flex w-100 justify-content-start align-items-center">
|
49
49
|
<span class="fas fa-file-medical me-3"></span>
|
50
|
-
<span class="menu-collapsed">ClinVar submissions</span>
|
50
|
+
<span class="menu-collapsed text-nowrap flex-grow-1">ClinVar submissions</span>
|
51
|
+
<span class="submenu-icon ms-auto"></span>
|
51
52
|
</div>
|
52
53
|
</a>
|
54
|
+
<div id='submenu1' class="collapse sidebar-submenu">
|
55
|
+
<div href="#" class="bg-dark list-group-item text-white">
|
56
|
+
|
57
|
+
<div class="d-flex flex-row flex-fill bd-highlight">
|
58
|
+
<div>
|
59
|
+
<span class="menu-collapsed">Germline</span>
|
60
|
+
</div>
|
61
|
+
<div>
|
62
|
+
<a href="{{ url_for('clinvar.clinvar_germline_submissions', institute_id=institute._id) }}" target="_blank" rel="noopener">
|
63
|
+
<span class="fa fa-link"></span></a>
|
64
|
+
</div>
|
65
|
+
</div>
|
66
|
+
|
67
|
+
<div class="d-flex flex-row flex-fill bd-highlight">
|
68
|
+
<div>
|
69
|
+
<span class="menu-collapsed">Oncogenic</span>
|
70
|
+
</div>
|
71
|
+
<div>
|
72
|
+
<a href="{{ url_for('clinvar.clinvar_onc_submissions', institute_id=institute._id) }}" target="_blank" rel="noopener">
|
73
|
+
<span class="fa fa-link"></span></a>
|
74
|
+
</div>
|
75
|
+
</div>
|
76
|
+
|
77
|
+
</div>
|
78
|
+
</div>
|
53
79
|
|
54
80
|
<!-- MME data menu item -->
|
55
81
|
<a href="{{ url_for('mme.mme_submissions', institute_id=institute._id) }}" class="bg-dark list-group-item list-group-item-action flex-column align-items-start">
|
@@ -25,7 +25,7 @@
|
|
25
25
|
</div>
|
26
26
|
<div class="col-md-1 mb-3">
|
27
27
|
{{ form.search_limit.label(class="form-label") }}
|
28
|
-
{{ form.search_limit(class="form-control") }}
|
28
|
+
{{ form.search_limit(class="form-control", type="number", min="1", step="1", required=True) }}
|
29
29
|
</div>
|
30
30
|
<div class="btn-sm mb-2 col-md-3 mx-auto">
|
31
31
|
{{ form.search(class="btn btn-primary mt-4") }}
|