scout-browser 4.101.0__py3-none-any.whl → 4.102.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/clinvar.py +7 -7
- scout/adapter/mongo/hgnc.py +7 -2
- scout/adapter/mongo/omics_variant.py +8 -0
- scout/adapter/mongo/variant_loader.py +6 -2
- scout/constants/__init__.py +1 -0
- scout/constants/igv_tracks.py +6 -2
- scout/constants/phenotype.py +1 -0
- scout/load/hpo.py +8 -2
- scout/server/blueprints/alignviewers/controllers.py +8 -6
- scout/server/blueprints/alignviewers/templates/alignviewers/igv_viewer.html +2 -0
- scout/server/blueprints/alignviewers/templates/alignviewers/utils.html +1 -1
- scout/server/blueprints/cases/controllers.py +56 -28
- scout/server/blueprints/cases/templates/cases/case_report.html +9 -88
- 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 +26 -24
- scout/server/blueprints/cases/views.py +32 -33
- scout/server/blueprints/clinvar/controllers.py +3 -2
- 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 +107 -73
- scout/server/blueprints/institutes/templates/overview/cases.html +1 -1
- scout/server/blueprints/login/controllers.py +2 -1
- scout/server/blueprints/login/views.py +5 -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 +10 -11
- scout/server/blueprints/variant/templates/variant/utils.html +1 -1
- scout/server/blueprints/variant/templates/variant/variant_details.html +68 -60
- scout/server/blueprints/variant/utils.py +25 -0
- scout/server/blueprints/variants/controllers.py +11 -42
- scout/server/blueprints/variants/views.py +9 -8
- scout/server/config.py +3 -0
- scout/server/extensions/beacon_extension.py +7 -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.102.0.dist-info}/METADATA +1 -1
- {scout_browser-4.101.0.dist-info → scout_browser-4.102.0.dist-info}/RECORD +45 -45
- {scout_browser-4.101.0.dist-info → scout_browser-4.102.0.dist-info}/WHEEL +0 -0
- {scout_browser-4.101.0.dist-info → scout_browser-4.102.0.dist-info}/entry_points.txt +0 -0
- {scout_browser-4.101.0.dist-info → scout_browser-4.102.0.dist-info}/licenses/LICENSE +0 -0
@@ -36,6 +36,7 @@ from scout.server.utils import (
|
|
36
36
|
html_to_pdf_file,
|
37
37
|
institute_and_case,
|
38
38
|
jsonconverter,
|
39
|
+
safe_redirect_back,
|
39
40
|
templated,
|
40
41
|
user_cases,
|
41
42
|
user_institutes,
|
@@ -148,7 +149,7 @@ def beacon_add_variants(institute_id, case_name):
|
|
148
149
|
store, institute_id, case_name
|
149
150
|
) # This function checks if user has permissions to access the case
|
150
151
|
beacon.add_variants(store, case_obj, request.form)
|
151
|
-
return
|
152
|
+
return safe_redirect_back(request)
|
152
153
|
|
153
154
|
|
154
155
|
@cases_bp.route("/beacon_remove_variants/<institute_id>/<case_name>", methods=["GET"])
|
@@ -158,7 +159,7 @@ def beacon_remove_variants(institute_id, case_name):
|
|
158
159
|
store, institute_id, case_name
|
159
160
|
) # This function checks if user has permissions to access the case
|
160
161
|
beacon.remove_variants(store, institute_id, case_obj)
|
161
|
-
return
|
162
|
+
return safe_redirect_back(request)
|
162
163
|
|
163
164
|
|
164
165
|
@cases_bp.route("/<institute_id>/<case_name>/mme_matches", methods=["GET", "POST"])
|
@@ -175,7 +176,7 @@ def matchmaker_match(institute_id, case_name, target):
|
|
175
176
|
"""Starts an internal match or a match against one or all MME external nodes"""
|
176
177
|
institute_obj, case_obj = institute_and_case(store, institute_id, case_name)
|
177
178
|
match_results = controllers.matchmaker_match(request, target, institute_id, case_name)
|
178
|
-
return
|
179
|
+
return safe_redirect_back(request)
|
179
180
|
|
180
181
|
|
181
182
|
@cases_bp.route("/<institute_id>/<case_name>/mme_add", methods=["POST"])
|
@@ -183,7 +184,7 @@ def matchmaker_add(institute_id, case_name):
|
|
183
184
|
"""Add or update a case in MatchMaker"""
|
184
185
|
# Call matchmaker_delete controller to add a patient to MatchMaker
|
185
186
|
controllers.matchmaker_add(request, institute_id, case_name)
|
186
|
-
return
|
187
|
+
return safe_redirect_back(request)
|
187
188
|
|
188
189
|
|
189
190
|
@cases_bp.route("/<institute_id>/<case_name>/mme_delete", methods=["POST"])
|
@@ -191,7 +192,7 @@ def matchmaker_delete(institute_id, case_name):
|
|
191
192
|
"""Remove a case from MatchMaker"""
|
192
193
|
# Call matchmaker_delete controller to delete a patient from MatchMaker
|
193
194
|
controllers.matchmaker_delete(request, institute_id, case_name)
|
194
|
-
return
|
195
|
+
return safe_redirect_back(request)
|
195
196
|
|
196
197
|
|
197
198
|
@cases_bp.route("/<institute_id>/<case_name>/individuals", methods=["POST"])
|
@@ -212,7 +213,7 @@ def update_individual(institute_id, case_name):
|
|
212
213
|
age=age,
|
213
214
|
tissue=tissue,
|
214
215
|
)
|
215
|
-
return
|
216
|
+
return safe_redirect_back(request)
|
216
217
|
|
217
218
|
|
218
219
|
@cases_bp.route("/<institute_id>/<case_name>/samples", methods=["POST"])
|
@@ -237,7 +238,7 @@ def update_cancer_sample(institute_id, case_name):
|
|
237
238
|
tumor_type=tumor_type,
|
238
239
|
tumor_purity=tumor_purity,
|
239
240
|
)
|
240
|
-
return
|
241
|
+
return safe_redirect_back(request)
|
241
242
|
|
242
243
|
|
243
244
|
@cases_bp.route("/<institute_id>/<case_name>/synopsis", methods=["POST"])
|
@@ -247,7 +248,7 @@ def case_synopsis(institute_id, case_name):
|
|
247
248
|
user_obj = store.user(current_user.email)
|
248
249
|
new_synopsis = request.form.get("synopsis")
|
249
250
|
controllers.update_synopsis(store, institute_obj, case_obj, user_obj, new_synopsis)
|
250
|
-
return
|
251
|
+
return safe_redirect_back(request)
|
251
252
|
|
252
253
|
|
253
254
|
@cases_bp.route("/api/v1/<institute_id>/<case_name>/case_report", methods=["GET"])
|
@@ -344,7 +345,7 @@ def mt_report(institute_id, case_name):
|
|
344
345
|
shutil.rmtree(temp_excel_dir)
|
345
346
|
|
346
347
|
flash("No MT report excel file could be exported for this sample", "warning")
|
347
|
-
return
|
348
|
+
return safe_redirect_back(request)
|
348
349
|
|
349
350
|
|
350
351
|
@cases_bp.route("/<institute_id>/<case_name>/diagnose", methods=["POST"])
|
@@ -366,7 +367,7 @@ def case_diagnosis(institute_id, case_name):
|
|
366
367
|
affected_inds=affected_inds,
|
367
368
|
remove=True if request.args.get("remove") == "yes" else False,
|
368
369
|
)
|
369
|
-
return
|
370
|
+
return safe_redirect_back(request, "#".join([link, "disease_assign"]))
|
370
371
|
|
371
372
|
|
372
373
|
@cases_bp.route("/<institute_id>/<case_name>/phenotypes", methods=["POST"])
|
@@ -414,7 +415,7 @@ def phenotypes(institute_id, case_name, phenotype_id=None):
|
|
414
415
|
)
|
415
416
|
return redirect(case_url)
|
416
417
|
|
417
|
-
return
|
418
|
+
return safe_redirect_back(request, "#".join([case_url, "phenotypes_panel"]))
|
418
419
|
|
419
420
|
|
420
421
|
@cases_bp.route("/<institute_id>/<case_name>/phenotype_export", methods=["POST"])
|
@@ -526,7 +527,7 @@ def phenotypes_actions(institute_id, case_name):
|
|
526
527
|
hgnc_ids = [result[0] for result in results if result[1] >= hpo_count]
|
527
528
|
store.update_dynamic_gene_list(case_obj, hgnc_ids=hgnc_ids, phenotype_ids=hpo_ids)
|
528
529
|
|
529
|
-
return
|
530
|
+
return safe_redirect_back(request, "#".join([case_url, "phenotypes_panel"]))
|
530
531
|
|
531
532
|
|
532
533
|
@cases_bp.route("/<institute_id>/<case_name>/events", methods=["POST"])
|
@@ -567,7 +568,7 @@ def events(institute_id, case_name, event_id=None):
|
|
567
568
|
# create a case comment
|
568
569
|
store.comment(institute_obj, case_obj, user_obj, link, content=content)
|
569
570
|
|
570
|
-
return
|
571
|
+
return safe_redirect_back(request)
|
571
572
|
|
572
573
|
|
573
574
|
@cases_bp.route("/<institute_id>/<case_name>/status", methods=["POST"])
|
@@ -588,7 +589,7 @@ def status(institute_id, case_name):
|
|
588
589
|
if tags or case_obj.get("tags") and tags != case_obj.get("tags"):
|
589
590
|
store.tag_case(institute_obj, case_obj, user_obj, tags, link)
|
590
591
|
|
591
|
-
return
|
592
|
+
return safe_redirect_back(request)
|
592
593
|
|
593
594
|
|
594
595
|
@cases_bp.route("/<institute_id>/<case_name>/assign", methods=["POST"])
|
@@ -605,7 +606,7 @@ def assign(institute_id, case_name, user_id=None, inactivate=False):
|
|
605
606
|
store.unassign(institute_obj, case_obj, user_obj, link, inactivate)
|
606
607
|
else:
|
607
608
|
store.assign(institute_obj, case_obj, user_obj, link)
|
608
|
-
return
|
609
|
+
return safe_redirect_back(request)
|
609
610
|
|
610
611
|
|
611
612
|
@cases_bp.route("/api/v1/cases", defaults={"institute_id": None})
|
@@ -688,7 +689,7 @@ def pin_variant(institute_id, case_name, variant_id):
|
|
688
689
|
store.pin_variant(institute_obj, case_obj, user_obj, link, variant_obj)
|
689
690
|
elif request.form["action"] == "DELETE":
|
690
691
|
store.unpin_variant(institute_obj, case_obj, user_obj, link, variant_obj)
|
691
|
-
return
|
692
|
+
return safe_redirect_back(request, request.referrer or link)
|
692
693
|
|
693
694
|
|
694
695
|
@cases_bp.route("/<institute_id>/<case_name>/<variant_id>/validate", methods=["POST"])
|
@@ -705,7 +706,7 @@ def mark_validation(institute_id, case_name, variant_id):
|
|
705
706
|
variant_id=variant_id,
|
706
707
|
)
|
707
708
|
store.validate(institute_obj, case_obj, user_obj, link, variant_obj, validate_type)
|
708
|
-
return
|
709
|
+
return safe_redirect_back(request, request.referrer or link)
|
709
710
|
|
710
711
|
|
711
712
|
@cases_bp.route(
|
@@ -748,8 +749,7 @@ def mark_causative(institute_id, case_name, variant_id, partial_causative=False)
|
|
748
749
|
store.unmark_causative(institute_obj, case_obj, user_obj, link, variant_obj)
|
749
750
|
|
750
751
|
# send the user back to the case that was marked as solved
|
751
|
-
|
752
|
-
return redirect(request.referrer)
|
752
|
+
return safe_redirect_back(request)
|
753
753
|
|
754
754
|
|
755
755
|
@cases_bp.route("/<institute_id>/<case_name>/check-case", methods=["POST"])
|
@@ -761,7 +761,7 @@ def check_case(institute_id, case_name):
|
|
761
761
|
store.case_collection.find_one_and_update(
|
762
762
|
{"_id": case_obj["_id"]}, {"$set": {"needs_check": False}}
|
763
763
|
)
|
764
|
-
return
|
764
|
+
return safe_redirect_back(request)
|
765
765
|
|
766
766
|
|
767
767
|
@cases_bp.route("/<institute_id>/<case_name>/report/<report_type>")
|
@@ -835,7 +835,7 @@ def share(institute_id, case_name):
|
|
835
835
|
except ValueError as ex:
|
836
836
|
flash(str(ex), "warning")
|
837
837
|
|
838
|
-
return
|
838
|
+
return safe_redirect_back(request)
|
839
839
|
|
840
840
|
|
841
841
|
@cases_bp.route("/<institute_id>/<case_name>/update_rerun_status")
|
@@ -846,7 +846,7 @@ def update_rerun_status(institute_id, case_name):
|
|
846
846
|
link = url_for("cases.case", institute_id=institute_id, case_name=case_name)
|
847
847
|
|
848
848
|
store.update_rerun_status(institute_obj, case_obj, user_obj, link)
|
849
|
-
return
|
849
|
+
return safe_redirect_back(request, link)
|
850
850
|
|
851
851
|
|
852
852
|
@cases_bp.route("/<institute_id>/<case_name>/monitor", methods=["POST"])
|
@@ -860,7 +860,7 @@ def rerun_monitor(institute_id, case_name):
|
|
860
860
|
else:
|
861
861
|
store.unmonitor(institute_obj, case_obj, user_obj, link)
|
862
862
|
|
863
|
-
return
|
863
|
+
return safe_redirect_back(request)
|
864
864
|
|
865
865
|
|
866
866
|
@cases_bp.route("/<institute_id>/<case_name>/reanalysis", methods=["POST"])
|
@@ -876,7 +876,7 @@ def reanalysis(institute_id, case_name):
|
|
876
876
|
LOG.error(msg)
|
877
877
|
flash(msg, "danger")
|
878
878
|
|
879
|
-
return
|
879
|
+
return safe_redirect_back(request)
|
880
880
|
|
881
881
|
|
882
882
|
@cases_bp.route("/<institute_id>/<case_name>/research", methods=["POST"])
|
@@ -886,7 +886,7 @@ def research(institute_id, case_name):
|
|
886
886
|
user_obj = store.user(current_user.email)
|
887
887
|
link = url_for(".case", institute_id=institute_id, case_name=case_name)
|
888
888
|
store.open_research(institute_obj, case_obj, user_obj, link)
|
889
|
-
return
|
889
|
+
return safe_redirect_back(request)
|
890
890
|
|
891
891
|
|
892
892
|
@cases_bp.route("/<institute_id>/<case_name>/reset_research", methods=["GET"])
|
@@ -895,7 +895,7 @@ def reset_research(institute_id, case_name):
|
|
895
895
|
user_obj = store.user(current_user.email)
|
896
896
|
link = url_for(".case", institute_id=institute_id, case_name=case_name)
|
897
897
|
store.reset_research(institute_obj, case_obj, user_obj, link)
|
898
|
-
return
|
898
|
+
return safe_redirect_back(request)
|
899
899
|
|
900
900
|
|
901
901
|
@cases_bp.route("/<institute_id>/<case_name>/cohorts", methods=["POST"])
|
@@ -909,7 +909,7 @@ def cohorts(institute_id, case_name):
|
|
909
909
|
store.remove_cohort(institute_obj, case_obj, user_obj, link, cohort_tag)
|
910
910
|
else:
|
911
911
|
store.add_cohort(institute_obj, case_obj, user_obj, link, cohort_tag)
|
912
|
-
return
|
912
|
+
return safe_redirect_back(request, "#".join([request.referrer, "cohorts"]))
|
913
913
|
|
914
914
|
|
915
915
|
@cases_bp.route("/<institute_id>/<case_name>/default-panels", methods=["POST"])
|
@@ -917,7 +917,7 @@ def default_panels(institute_id, case_name):
|
|
917
917
|
"""Update default panels for a case."""
|
918
918
|
panel_ids = request.form.getlist("panel_ids")
|
919
919
|
controllers.update_default_panels(store, current_user, institute_id, case_name, panel_ids)
|
920
|
-
return
|
920
|
+
return safe_redirect_back(request)
|
921
921
|
|
922
922
|
|
923
923
|
@cases_bp.route("/<institute_id>/<case_name>/update-clinical-filter-hpo", methods=["POST"])
|
@@ -928,7 +928,7 @@ def update_clinical_filter_hpo(institute_id, case_name):
|
|
928
928
|
controllers.update_clinical_filter_hpo(
|
929
929
|
store, current_user, institute_obj, case_obj, hpo_clinical_filter
|
930
930
|
)
|
931
|
-
return
|
931
|
+
return safe_redirect_back(request)
|
932
932
|
|
933
933
|
|
934
934
|
@cases_bp.route("/<institute_id>/<case_name>/add_case_group", methods=["GET", "POST"])
|
@@ -943,8 +943,7 @@ def add_case_group(institute_id, case_name):
|
|
943
943
|
case_name = request.form.get("other_case_name")
|
944
944
|
|
945
945
|
controllers.add_case_group(store, current_user, institute_id, case_name, group_id)
|
946
|
-
|
947
|
-
return redirect(request.referrer + "#case_groups")
|
946
|
+
return safe_redirect_back(request, request.referrer + "#case_groups")
|
948
947
|
|
949
948
|
|
950
949
|
@cases_bp.route("/<institute_id>/<case_name>/<case_group>/remove_case_group", methods=["GET"])
|
@@ -952,7 +951,7 @@ def remove_case_group(institute_id, case_name, case_group):
|
|
952
951
|
"""Unbind a case group from a case. Remove the group if it is no longer in use."""
|
953
952
|
controllers.remove_case_group(store, current_user, institute_id, case_name, case_group)
|
954
953
|
|
955
|
-
return
|
954
|
+
return safe_redirect_back(request, request.referrer + "#case_groups")
|
956
955
|
|
957
956
|
|
958
957
|
@cases_bp.route("/<case_group>/case_group_update_label", methods=["POST"])
|
@@ -962,7 +961,7 @@ def case_group_update_label(case_group):
|
|
962
961
|
|
963
962
|
controllers.case_group_update_label(store, case_group, label)
|
964
963
|
|
965
|
-
return
|
964
|
+
return safe_redirect_back(request, request.referrer + "#case_groups")
|
966
965
|
|
967
966
|
|
968
967
|
@cases_bp.route("/<institute_id>/<case_name>/download-hpo-genes/<category>", methods=["GET"])
|
@@ -19,6 +19,7 @@ from scout.constants.variant_tags import MANUAL_RANK_OPTIONS
|
|
19
19
|
from scout.models.clinvar import clinvar_variant
|
20
20
|
from scout.server.blueprints.variant.utils import add_gene_info
|
21
21
|
from scout.server.extensions import clinvar_api, store
|
22
|
+
from scout.server.utils import get_case_genome_build
|
22
23
|
from scout.utils.hgvs import validate_hgvs
|
23
24
|
from scout.utils.scout_requests import fetch_refseq_version
|
24
25
|
|
@@ -30,7 +31,7 @@ LOG = logging.getLogger(__name__)
|
|
30
31
|
def _get_var_tx_hgvs(case_obj: dict, variant_obj: dict) -> List[Tuple[str, str]]:
|
31
32
|
"""Retrieve all transcripts / HGVS for a given variant."""
|
32
33
|
|
33
|
-
build =
|
34
|
+
build = get_case_genome_build(case_obj)
|
34
35
|
tx_hgvs_list = [("", "Do not specify")]
|
35
36
|
case_has_build_37 = "37" in case_obj.get("genome_build", "37")
|
36
37
|
|
@@ -399,7 +400,7 @@ def json_api_submission(submission_id):
|
|
399
400
|
|
400
401
|
# Retrieve genome build for the case submitted
|
401
402
|
case_obj = store.case(case_id=variant_data[0].get("case_id")) or {"genome_build": 37}
|
402
|
-
extra_params["assembly"] = "GRCh37" if "37" in
|
403
|
+
extra_params["assembly"] = "GRCh37" if "37" in get_case_genome_build(case_obj) else "GRCh38"
|
403
404
|
|
404
405
|
def _write_file(afile, header, lines): # Write temp CSV file
|
405
406
|
writes = csv.writer(afile, delimiter=",", quoting=csv.QUOTE_ALL)
|
@@ -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,28 @@ VAR_SPECIFIC_EVENTS = [
|
|
47
51
|
"cancel_sanger",
|
48
52
|
]
|
49
53
|
|
54
|
+
# Projection for fetching cases
|
55
|
+
ALL_CASES_PROJECTION = {
|
56
|
+
"analysis_date": 1,
|
57
|
+
"assignees": 1,
|
58
|
+
"beacon": 1,
|
59
|
+
"case_id": 1,
|
60
|
+
"display_name": 1,
|
61
|
+
"genome_build": 1,
|
62
|
+
"individuals": 1,
|
63
|
+
"is_rerun": 1,
|
64
|
+
"is_research": 1,
|
65
|
+
"mme_submission": 1,
|
66
|
+
"owner": 1,
|
67
|
+
"panels": 1,
|
68
|
+
"phenotype_terms": 1,
|
69
|
+
"rank_model_version": 1,
|
70
|
+
"status": 1,
|
71
|
+
"sv_rank_model_version": 1,
|
72
|
+
"track": 1,
|
73
|
+
"vcf_files": 1,
|
74
|
+
}
|
75
|
+
|
50
76
|
|
51
77
|
def get_timeline_data(limit):
|
52
78
|
"""Retrieve chronologially ordered events from the database to display them in the timeline page
|
@@ -519,92 +545,106 @@ def export_case_samples(institute_id, filtered_cases) -> Response:
|
|
519
545
|
)
|
520
546
|
|
521
547
|
|
522
|
-
def
|
523
|
-
|
524
|
-
|
548
|
+
def get_cases_by_query(
|
549
|
+
store: MongoAdapter,
|
550
|
+
request: request,
|
551
|
+
institute_id: str,
|
552
|
+
) -> list:
|
553
|
+
"""Fetch additional cases based on filters given in request query form.
|
525
554
|
|
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())
|
555
|
+
Sets metadata in data, e.g. sort order.
|
556
|
+
"""
|
532
557
|
|
533
|
-
|
534
|
-
|
535
|
-
|
558
|
+
name_query = request.form
|
559
|
+
all_cases = store.cases(
|
560
|
+
collaborator=institute_id,
|
561
|
+
name_query=name_query,
|
562
|
+
skip_assigned=request.form.get("skip_assigned"),
|
563
|
+
is_research=request.form.get("is_research"),
|
564
|
+
has_rna_data=request.form.get("has_rna"),
|
565
|
+
verification_pending=request.form.get("validation_ordered"),
|
566
|
+
has_clinvar_submission=request.form.get("clinvar_submitted"),
|
567
|
+
projection=ALL_CASES_PROJECTION,
|
568
|
+
)
|
569
|
+
return all_cases
|
536
570
|
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
"rank_model_version": 1,
|
553
|
-
"status": 1,
|
554
|
-
"sv_rank_model_version": 1,
|
555
|
-
"track": 1,
|
556
|
-
"vcf_files": 1,
|
557
|
-
}
|
571
|
+
|
572
|
+
def get_and_set_cases_by_status(
|
573
|
+
store: MongoAdapter,
|
574
|
+
request: request,
|
575
|
+
institute_obj: dict,
|
576
|
+
previous_query_result_cases: list,
|
577
|
+
data: dict,
|
578
|
+
) -> dict:
|
579
|
+
"""Process cases for statuses that require all cases to be shown.
|
580
|
+
Group cases by status, process additional cases for the remaining statuses
|
581
|
+
and ensure that we don't dim cases that already appeared in query search results.
|
582
|
+
"""
|
583
|
+
|
584
|
+
status_show_all_cases = institute_obj.get("show_all_cases_status", ["prioritized"])
|
585
|
+
nr_cases_showall_statuses = 0
|
558
586
|
|
559
587
|
# Group cases by status
|
560
588
|
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
589
|
|
564
|
-
# Process cases for statuses that require all cases to be shown
|
565
590
|
for status in status_show_all_cases:
|
566
591
|
cases_in_status = store.cases_by_status(
|
567
|
-
institute_id=
|
592
|
+
institute_id=institute_obj["_id"], status=status, projection=ALL_CASES_PROJECTION
|
568
593
|
)
|
569
594
|
cases_in_status = _sort_cases(data, request, cases_in_status)
|
570
595
|
for case_obj in cases_in_status:
|
571
596
|
populate_case_obj(case_obj, store)
|
597
|
+
case_obj["dimmed_in_search"] = True
|
572
598
|
case_groups[status].append(case_obj)
|
573
599
|
nr_cases_showall_statuses += 1
|
574
600
|
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
601
|
+
nr_name_query_matching_displayed_cases = 0
|
602
|
+
limit = int(request.form.get("search_limit", 100))
|
603
|
+
for case_obj in previous_query_result_cases:
|
604
|
+
if case_obj["status"] in status_show_all_cases:
|
605
|
+
for group_case in case_groups[status]:
|
606
|
+
if group_case["_id"] == case_obj["_id"]:
|
607
|
+
group_case["dimmed_in_search"] = False
|
608
|
+
elif nr_name_query_matching_displayed_cases == limit:
|
609
|
+
break
|
610
|
+
else:
|
611
|
+
populate_case_obj(case_obj, store)
|
612
|
+
case_groups[case_obj["status"]].append(case_obj)
|
613
|
+
nr_name_query_matching_displayed_cases += 1
|
614
|
+
|
615
|
+
data["found_cases"] = nr_name_query_matching_displayed_cases + nr_cases_showall_statuses
|
616
|
+
data["limit"] = limit
|
617
|
+
return case_groups
|
618
|
+
|
619
|
+
|
620
|
+
def cases(store: MongoAdapter, request: request, institute_id: str):
|
621
|
+
"""Preprocess case objects for the 'cases' view.
|
622
|
+
|
623
|
+
Returns data dict for view display, or response in case of file export.
|
624
|
+
"""
|
625
|
+
data = {}
|
626
|
+
|
627
|
+
# Initialize data (institute info, filters, and case counts)
|
628
|
+
institute_obj = institute_and_case(store, institute_id)
|
629
|
+
data["institute"] = institute_obj
|
630
|
+
data["form"] = CaseFilterForm(request.form)
|
631
|
+
data["status_ncases"] = store.nr_cases_by_status(institute_id=institute_id)
|
632
|
+
data["nr_cases"] = sum(data["status_ncases"].values())
|
633
|
+
|
634
|
+
# Fetch Sanger unevaluated and validated cases
|
635
|
+
sanger_ordered_not_validated = get_sanger_unevaluated(store, institute_id, current_user.email)
|
636
|
+
data["sanger_unevaluated"], data["sanger_validated_by_others"] = sanger_ordered_not_validated
|
637
|
+
|
638
|
+
all_cases = get_cases_by_query(store, request, institute_id)
|
588
639
|
all_cases = _sort_cases(data, request, all_cases)
|
589
640
|
|
590
641
|
if request.form.get("export"):
|
591
642
|
return export_case_samples(institute_id, all_cases)
|
592
643
|
|
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
|
644
|
+
case_groups = get_and_set_cases_by_status(store, request, institute_obj, all_cases, data)
|
602
645
|
|
603
646
|
# Compile the final data
|
604
647
|
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
648
|
return data
|
609
649
|
|
610
650
|
|
@@ -615,7 +655,9 @@ def populate_case_obj(case_obj: dict, store: MongoAdapter):
|
|
615
655
|
analysis_types = set(["mixed"])
|
616
656
|
case_obj["analysis_types"] = list(analysis_types)
|
617
657
|
|
618
|
-
case_obj["assignees"] = [
|
658
|
+
case_obj["assignees"] = [
|
659
|
+
store.user(user_id=user_id) for user_id in case_obj.get("assignees", [])
|
660
|
+
]
|
619
661
|
|
620
662
|
last_analysis_date = case_obj.get("analysis_date", datetime.datetime.now())
|
621
663
|
all_analyses_dates = {
|
@@ -821,7 +863,7 @@ def gene_variants(store, pymongo_cursor, variant_count, page=1, per_page=50):
|
|
821
863
|
case_display_name = variant_case_obj.get("display_name")
|
822
864
|
variant_obj["case_display_name"] = case_display_name
|
823
865
|
|
824
|
-
genome_build =
|
866
|
+
genome_build = get_case_genome_build(variant_case_obj)
|
825
867
|
update_variant_genes(store, variant_obj, genome_build)
|
826
868
|
variants.append(variant_obj)
|
827
869
|
|
@@ -885,14 +927,6 @@ def update_variant_genes(store, variant_obj, genome_build):
|
|
885
927
|
variant_obj["functional_annotations"] = get_annotations(gene_symbols, functional_annotations)
|
886
928
|
|
887
929
|
|
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
930
|
def get_hgvs(gene_obj: Dict) -> Tuple[str, str, str]:
|
897
931
|
"""Analyse gene object for hgvs info
|
898
932
|
Return:
|
@@ -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 %}
|
@@ -98,7 +98,7 @@ def google_login() -> Optional[Response]:
|
|
98
98
|
|
99
99
|
redirect_uri: str = url_for("login.authorized", _external=True)
|
100
100
|
try:
|
101
|
-
return oauth_client.google.authorize_redirect(redirect_uri)
|
101
|
+
return oauth_client.google.authorize_redirect(redirect_uri, prompt="select_account")
|
102
102
|
except Exception:
|
103
103
|
flash("An error has occurred while logging in user using Google OAuth", "warning")
|
104
104
|
return None
|
@@ -151,6 +151,7 @@ def perform_flask_login(user_dict: "LoginUser") -> Response:
|
|
151
151
|
|
152
152
|
def logout_oidc_user(session, provider: str):
|
153
153
|
"""Log out a user from an OIDC login provider-"""
|
154
|
+
|
154
155
|
logout_url = current_app.config[provider].get("logout_url")
|
155
156
|
if not logout_url or not session.get("token_response"):
|
156
157
|
return
|