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.
Files changed (87) hide show
  1. scout/adapter/mongo/case.py +26 -122
  2. scout/adapter/mongo/clinvar.py +98 -32
  3. scout/adapter/mongo/event.py +0 -47
  4. scout/adapter/mongo/hgnc.py +7 -2
  5. scout/adapter/mongo/omics_variant.py +8 -0
  6. scout/adapter/mongo/variant_loader.py +12 -4
  7. scout/build/variant/variant.py +1 -0
  8. scout/commands/load/variants.py +1 -1
  9. scout/commands/update/user.py +87 -49
  10. scout/constants/__init__.py +4 -0
  11. scout/constants/clinvar.py +10 -0
  12. scout/constants/igv_tracks.py +6 -2
  13. scout/constants/phenotype.py +1 -0
  14. scout/constants/variant_tags.py +18 -0
  15. scout/demo/NIST.trgt.stranger.vcf.gz +0 -0
  16. scout/demo/NIST.trgt.stranger.vcf.gz.tbi +0 -0
  17. scout/demo/__init__.py +1 -0
  18. scout/load/hpo.py +8 -2
  19. scout/models/clinvar.py +86 -0
  20. scout/parse/variant/coordinates.py +5 -1
  21. scout/parse/variant/gene.py +5 -9
  22. scout/parse/variant/genotype.py +66 -42
  23. scout/parse/variant/variant.py +2 -0
  24. scout/server/app.py +71 -2
  25. scout/server/blueprints/alignviewers/controllers.py +8 -6
  26. scout/server/blueprints/alignviewers/templates/alignviewers/igv_viewer.html +4 -0
  27. scout/server/blueprints/alignviewers/templates/alignviewers/utils.html +1 -1
  28. scout/server/blueprints/cases/controllers.py +57 -29
  29. scout/server/blueprints/cases/templates/cases/case_report.html +28 -90
  30. scout/server/blueprints/cases/templates/cases/matchmaker.html +1 -1
  31. scout/server/blueprints/cases/templates/cases/phenotype.html +1 -1
  32. scout/server/blueprints/cases/templates/cases/utils.html +34 -53
  33. scout/server/blueprints/cases/views.py +32 -33
  34. scout/server/blueprints/clinvar/controllers.py +235 -54
  35. scout/server/blueprints/clinvar/form.py +38 -1
  36. scout/server/blueprints/clinvar/static/form_style.css +8 -1
  37. scout/server/blueprints/clinvar/templates/clinvar/clinvar_onc_submissions.html +200 -0
  38. scout/server/blueprints/clinvar/templates/clinvar/clinvar_submissions.html +3 -2
  39. scout/server/blueprints/clinvar/templates/clinvar/components.html +198 -0
  40. scout/server/blueprints/clinvar/templates/clinvar/multistep_add_onc_variant.html +187 -0
  41. scout/server/blueprints/clinvar/templates/clinvar/multistep_add_variant.html +9 -348
  42. scout/server/blueprints/clinvar/templates/clinvar/scripts.html +193 -0
  43. scout/server/blueprints/clinvar/views.py +90 -13
  44. scout/server/blueprints/diagnoses/controllers.py +4 -8
  45. scout/server/blueprints/diagnoses/templates/diagnoses/diagnoses.html +1 -1
  46. scout/server/blueprints/diagnoses/templates/diagnoses/disease_term.html +1 -1
  47. scout/server/blueprints/diagnoses/views.py +2 -2
  48. scout/server/blueprints/institutes/controllers.py +148 -75
  49. scout/server/blueprints/institutes/forms.py +1 -0
  50. scout/server/blueprints/institutes/templates/overview/cases.html +1 -1
  51. scout/server/blueprints/institutes/templates/overview/gene_variants.html +15 -6
  52. scout/server/blueprints/institutes/templates/overview/institute_sidebar.html +28 -2
  53. scout/server/blueprints/institutes/templates/overview/utils.html +1 -1
  54. scout/server/blueprints/institutes/views.py +17 -4
  55. scout/server/blueprints/login/controllers.py +2 -1
  56. scout/server/blueprints/login/views.py +5 -2
  57. scout/server/blueprints/mme/templates/mme/mme_submissions.html +2 -2
  58. scout/server/blueprints/omics_variants/templates/omics_variants/outliers.html +2 -2
  59. scout/server/blueprints/omics_variants/views.py +2 -2
  60. scout/server/blueprints/phenotypes/controllers.py +15 -2
  61. scout/server/blueprints/phenotypes/templates/phenotypes/hpo_terms.html +1 -1
  62. scout/server/blueprints/variant/controllers.py +11 -12
  63. scout/server/blueprints/variant/templates/variant/cancer-variant.html +2 -1
  64. scout/server/blueprints/variant/templates/variant/components.html +0 -1
  65. scout/server/blueprints/variant/templates/variant/sv-variant.html +2 -1
  66. scout/server/blueprints/variant/templates/variant/utils.html +1 -1
  67. scout/server/blueprints/variant/templates/variant/variant.html +2 -2
  68. scout/server/blueprints/variant/templates/variant/variant_details.html +100 -84
  69. scout/server/blueprints/variant/utils.py +25 -0
  70. scout/server/blueprints/variants/controllers.py +11 -42
  71. scout/server/blueprints/variants/templates/variants/cancer-variants.html +5 -3
  72. scout/server/blueprints/variants/templates/variants/str-variants.html +4 -1
  73. scout/server/blueprints/variants/templates/variants/sv-variants.html +3 -3
  74. scout/server/blueprints/variants/templates/variants/utils.html +4 -0
  75. scout/server/blueprints/variants/templates/variants/variants.html +4 -4
  76. scout/server/blueprints/variants/views.py +9 -8
  77. scout/server/config.py +3 -0
  78. scout/server/extensions/beacon_extension.py +7 -2
  79. scout/server/extensions/clinvar_extension.py +2 -2
  80. scout/server/templates/bootstrap_global.html +11 -1
  81. scout/server/templates/layout.html +6 -1
  82. scout/server/utils.py +24 -3
  83. {scout_browser-4.101.0.dist-info → scout_browser-4.103.0.dist-info}/METADATA +1 -1
  84. {scout_browser-4.101.0.dist-info → scout_browser-4.103.0.dist-info}/RECORD +87 -81
  85. {scout_browser-4.101.0.dist-info → scout_browser-4.103.0.dist-info}/WHEEL +0 -0
  86. {scout_browser-4.101.0.dist-info → scout_browser-4.103.0.dist-info}/entry_points.txt +0 -0
  87. {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'Response from ClinVar: {clinvar_api.json_submission_status( submission_id=submission_id, api_key=request.form.get("apiKey"))}',
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>/clinvar_submissions", methods=["GET"])
89
- def clinvar_submissions(institute_id):
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.clinvar_submissions(institute_id),
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=clinvar_id, suffix=".json")
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"{clinvar_id}.json",
162
+ download_name=f"{filename}.json",
149
163
  mimetype="application/json",
150
164
  as_attachment=True,
151
165
  )
152
166
  else:
153
- flash(f"JSON file could not be crated for ClinVar submission: {clinvar_id} ", "warning")
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
- data = {"terms": list(disease_data)}
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="https://hpo.jax.org/app/browse/term/{{term}}" target="_blank" rel="noopener" ></a>
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="http://hpo.jax.org/app/browse/term/{{pheno.hpo_id}}" target="_blank">{{ pheno.hpo_id }}</a>
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
- data = {"counts": store.disease_terminology_count()}
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 institute_and_case, user_institutes
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 cases(store: MongoAdapter, request: request, institute_id: str) -> dict:
523
- """Preprocess case objects for the 'cases' view."""
524
- data = {}
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
- # Initialize data (institute info, filters, and case counts)
527
- institute_obj = institute_and_case(store, institute_id)
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
- # Fetch Sanger unevaluated and validated cases
534
- sanger_ordered_not_validated = get_sanger_unevaluated(store, institute_id, current_user.email)
535
- data["sanger_unevaluated"], data["sanger_validated_by_others"] = sanger_ordered_not_validated
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
- # Projection for fetching cases
538
- ALL_CASES_PROJECTION = {
539
- "analysis_date": 1,
540
- "assignees": 1,
541
- "beacon": 1,
542
- "case_id": 1,
543
- "display_name": 1,
544
- "genome_build": 1,
545
- "individuals": 1,
546
- "is_rerun": 1,
547
- "is_research": 1,
548
- "mme_submission": 1,
549
- "owner": 1,
550
- "panels": 1,
551
- "phenotype_terms": 1,
552
- "rank_model_version": 1,
553
- "status": 1,
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=institute_id, status=status, projection=ALL_CASES_PROJECTION
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
- # Fetch additional cases based on filters
576
- name_query = request.form
577
- limit = int(request.form.get("search_limit")) if request.form.get("search_limit") else 100
578
- all_cases = store.cases(
579
- collaborator=institute_id,
580
- name_query=name_query,
581
- skip_assigned=request.form.get("skip_assigned"),
582
- is_research=request.form.get("is_research"),
583
- has_rna_data=request.form.get("has_rna"),
584
- verification_pending=request.form.get("validation_ordered"),
585
- has_clinvar_submission=request.form.get("clinvar_submitted"),
586
- projection=ALL_CASES_PROJECTION,
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
- # Process additional cases for the remaining statuses
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"] = [store.user(user_email) for user_email in case_obj.get("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.case_to_clinVars(case_obj["_id"])
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(variant.get("hgvs", "-")) # HGVS
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 = get_genome_build(variant_case_obj)
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
- <a href="{{ url_for('variant.variant', institute_id=variant.institute,
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
- {{ (variant.hgvs or '')|url_decode }}
102
- {% endif %}
103
- </a>
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.rank_score|int }}</span>
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="{{ url_for('clinvar.clinvar_submissions', institute_id=institute._id) }}" class="bg-dark list-group-item list-group-item-action flex-column align-items-start">
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") }}