scout-browser 4.90.1__py3-none-any.whl → 4.91.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scout/__version__.py +1 -1
- scout/adapter/mongo/case.py +27 -38
- scout/commands/export/variant.py +14 -4
- scout/commands/load/panel.py +2 -1
- scout/commands/update/panelapp.py +11 -3
- scout/commands/view/case.py +2 -2
- scout/constants/__init__.py +1 -2
- scout/constants/acmg.py +15 -15
- scout/constants/case_tags.py +0 -46
- scout/constants/clnsig.py +2 -1
- scout/constants/gene_tags.py +0 -6
- scout/constants/panels.py +16 -0
- scout/constants/variants_export.py +2 -0
- scout/demo/__init__.py +4 -1
- scout/demo/panelapp_panel.json +463 -0
- scout/demo/panelapp_panels_reduced.json +37 -0
- scout/load/panel.py +3 -142
- scout/load/panelapp.py +138 -0
- scout/models/case/case_loading_models.py +5 -4
- scout/parse/panel.py +3 -117
- scout/parse/panelapp.py +112 -0
- scout/parse/variant/clnsig.py +26 -21
- scout/parse/variant/genotype.py +6 -5
- scout/server/blueprints/alignviewers/controllers.py +7 -5
- scout/server/blueprints/alignviewers/templates/alignviewers/utils.html +1 -1
- scout/server/blueprints/cases/templates/cases/case_sma.html +49 -42
- scout/server/blueprints/cases/templates/cases/collapsible_actionbar.html +27 -12
- scout/server/blueprints/cases/views.py +18 -7
- scout/server/blueprints/clinvar/templates/clinvar/clinvar_submissions.html +7 -7
- scout/server/blueprints/clinvar/templates/clinvar/multistep_add_variant.html +2 -2
- scout/server/blueprints/dashboard/controllers.py +128 -165
- scout/server/blueprints/dashboard/forms.py +3 -13
- scout/server/blueprints/dashboard/templates/dashboard/dashboard_general.html +17 -22
- scout/server/blueprints/institutes/forms.py +1 -2
- scout/server/blueprints/institutes/templates/overview/cases.html +2 -133
- scout/server/blueprints/institutes/templates/overview/utils.html +135 -0
- scout/server/blueprints/omics_variants/templates/omics_variants/outliers.html +5 -0
- scout/server/blueprints/panels/templates/panels/panel.html +5 -1
- scout/server/blueprints/panels/templates/panels/panel_pdf_simple.html +5 -1
- scout/server/blueprints/variant/controllers.py +6 -1
- scout/server/blueprints/variant/templates/variant/buttons.html +11 -10
- scout/server/blueprints/variant/templates/variant/components.html +63 -44
- scout/server/blueprints/variant/templates/variant/str-variant-reviewer.html +1 -1
- scout/server/blueprints/variant/templates/variant/utils.html +38 -10
- scout/server/blueprints/variant/templates/variant/variant.html +1 -1
- scout/server/blueprints/variants/controllers.py +9 -4
- scout/server/blueprints/variants/templates/variants/cancer-sv-variants.html +10 -6
- scout/server/blueprints/variants/templates/variants/cancer-variants.html +6 -17
- scout/server/blueprints/variants/templates/variants/str-variants.html +2 -2
- scout/server/blueprints/variants/templates/variants/sv-variants.html +7 -4
- scout/server/blueprints/variants/templates/variants/utils.html +14 -0
- scout/server/extensions/__init__.py +2 -0
- scout/server/extensions/panelapp_extension.py +75 -0
- scout/server/links.py +19 -1
- scout/server/utils.py +25 -33
- {scout_browser-4.90.1.dist-info → scout_browser-4.91.1.dist-info}/METADATA +1 -1
- {scout_browser-4.90.1.dist-info → scout_browser-4.91.1.dist-info}/RECORD +61 -57
- {scout_browser-4.90.1.dist-info → scout_browser-4.91.1.dist-info}/WHEEL +1 -1
- scout/demo/panelapp_test_panel.json +0 -79
- {scout_browser-4.90.1.dist-info → scout_browser-4.91.1.dist-info}/LICENSE +0 -0
- {scout_browser-4.90.1.dist-info → scout_browser-4.91.1.dist-info}/entry_points.txt +0 -0
- {scout_browser-4.90.1.dist-info → scout_browser-4.91.1.dist-info}/top_level.txt +0 -0
@@ -197,22 +197,37 @@
|
|
197
197
|
<span class="menu-collapsed">Genome build {{ case.genome_build }}</span>
|
198
198
|
</div>
|
199
199
|
</div>
|
200
|
-
<div href="#" class="bg-dark list-group-item d-inline-block text-white"
|
201
|
-
|
200
|
+
<div href="#" class="bg-dark list-group-item d-inline-block text-white"
|
201
|
+
{% if not case.bam_files %} data-bs-toggle="tooltip" title="Alignment file(s) missing" {% endif %}>
|
202
202
|
<form action="{{url_for('alignviewers.igv', institute_id=case['owner'], case_name=case['display_name']) }}" target="_blank" rel="noopener">
|
203
|
-
<button
|
203
|
+
<button type="submit" class="btn btn-xs form-control btn-secondary"
|
204
|
+
{% if not case.bam_files %}
|
205
|
+
disabled="disabled"><span class="fa fa-times-circle fa-fw me-1"></span>
|
206
|
+
{% else %}><span class="fa fa-magnifying-glass fa-fw me-1"></span>
|
207
|
+
{% endif %}
|
208
|
+
<span class="menu-collapsed">IGV g</span>D<span class="menu-collapsed">NA</span>
|
209
|
+
</button>
|
204
210
|
</form>
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
211
|
+
</div>
|
212
|
+
<div href="#" class="bg-dark list-group-item d-inline-block text-white"
|
213
|
+
{% if not case.mt_bams %} data-bs-toggle="tooltip" title="Alignment file(s) missing" {% endif %}>
|
214
|
+
<form action="{{url_for('alignviewers.igv', institute_id=case['owner'], case_name=case['display_name'], chrom='M', start=1, stop=16569) }}" target="_blank" rel="noopener">
|
215
|
+
<button type="submit" class="btn btn-xs form-control btn-secondary"
|
216
|
+
{% if not case.mt_bams %}
|
217
|
+
disabled="disabled"><span class="fa fa-times-circle fa-fw me-1"></span>
|
218
|
+
{% else %}
|
219
|
+
><span class="fa fa-magnifying-glass fa-fw me-1"></span>
|
220
|
+
{% endif %}
|
221
|
+
<span class="menu-collapsed">IGV </span>m<span class="menu-collapsed">tDNA</span></button>
|
213
222
|
</form>
|
214
223
|
</div>
|
215
|
-
{%
|
224
|
+
{% if has_rna_tracks %}
|
225
|
+
<div href="#" class="bg-dark list-group-item d-inline-block text-white">
|
226
|
+
<form action="{{url_for('alignviewers.sashimi_igv', institute_id=case['owner'], case_name=case['display_name']) }}" target="_blank" rel="noopener">
|
227
|
+
<button type="submit" class="btn btn-xs form-control btn-secondary" data-bs-toggle="tooltip" data-bs-placement="top" title="Available in build GRCh{{ case.rna_genome_build or '38' }}"><span class="fa fa-magnifying-glass fa-fw me-1"></span><span class="menu-collapsed">IGV </span>R<span class="menu-collapsed">NA</span></button>
|
228
|
+
</form>
|
229
|
+
</div>
|
230
|
+
{% endif %}
|
216
231
|
{% endmacro %}
|
217
232
|
|
218
233
|
{% macro rank_model(case) %}
|
@@ -25,6 +25,8 @@ from flask import (
|
|
25
25
|
url_for,
|
26
26
|
)
|
27
27
|
from flask_login import current_user
|
28
|
+
from pymongo.errors import OperationFailure
|
29
|
+
from werkzeug.datastructures import ImmutableMultiDict
|
28
30
|
|
29
31
|
from scout.constants import DATE_DAY_FORMATTER
|
30
32
|
from scout.server.blueprints.variants.controllers import activate_case
|
@@ -619,7 +621,13 @@ def caselist(institute_id):
|
|
619
621
|
if query is None:
|
620
622
|
return abort(500)
|
621
623
|
|
622
|
-
|
624
|
+
name_query = ImmutableMultiDict(
|
625
|
+
{
|
626
|
+
"case": query,
|
627
|
+
}
|
628
|
+
)
|
629
|
+
|
630
|
+
matching_cases = store.cases(owner=institute_id, name_query=name_query)
|
623
631
|
display_names = []
|
624
632
|
if institute_id: # called from case page, where institute_id is provided
|
625
633
|
display_names = sorted([case["display_name"] for case in matching_cases])
|
@@ -643,13 +651,16 @@ def hpoterms():
|
|
643
651
|
query = request.args.get("query")
|
644
652
|
if query is None:
|
645
653
|
return abort(500)
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
654
|
+
try:
|
655
|
+
terms = sorted(store.hpo_terms(query=query), key=itemgetter("hpo_number"))
|
656
|
+
json_terms = [
|
657
|
+
{"name": "{} | {}".format(term["_id"], term["description"]), "id": term["_id"]}
|
658
|
+
for term in terms[:7]
|
659
|
+
]
|
651
660
|
|
652
|
-
|
661
|
+
return jsonify(json_terms)
|
662
|
+
except OperationFailure as of:
|
663
|
+
return jsonify({"error": of._message})
|
653
664
|
|
654
665
|
|
655
666
|
@cases_bp.route("/api/v1/disease-terms")
|
@@ -101,8 +101,8 @@
|
|
101
101
|
{% endif %}
|
102
102
|
{% if subm_obj.status == 'submitted' %} <!--Submission status query -->
|
103
103
|
<form id="statusApi_{{subm_obj._id}}" action="{{ url_for('clinvar.clinvar_submission_status', submission_id=subm_obj.clinvar_subm_id) }}" method="POST">
|
104
|
-
{{ status_modal() }}
|
105
|
-
<td style="width: 20%"><button type="button" class="btn btn-sm btn-secondary form-control" name="status_enquiry" value="api_status" data-bs-toggle="modal" data-bs-target="#
|
104
|
+
{{ status_modal(subm_obj._id) }}
|
105
|
+
<td style="width: 20%"><button type="button" class="btn btn-sm btn-secondary form-control" name="status_enquiry" value="api_status" data-bs-toggle="modal" data-bs-target="#statusModal_{{subm_obj._id}}">Submission status enquiry</button></td>
|
106
106
|
</form>
|
107
107
|
{% endif %}
|
108
108
|
</tr>
|
@@ -156,7 +156,7 @@
|
|
156
156
|
<td><a href="{{ url_for('cases.case', institute_id=institute._id, case_name=subm_obj.cases[subm_variant.case_id]) }}" target="_blank" rel="noopener">{{subm_obj.cases[subm_variant.case_id]}}</a></td>
|
157
157
|
<td>{{subm_variant.ref_seq or '-'}}</td>
|
158
158
|
<td>{{subm_variant.gene_symbol or '-'}}</td>
|
159
|
-
<td>{{subm_variant.hgvs|truncate(
|
159
|
+
<td>{{subm_variant.hgvs|truncate(100,true,'..') or '-'}}</td>
|
160
160
|
<td>{{subm_variant.clinsig}}</td>
|
161
161
|
<td>{{subm_variant.added_by or "N/A"}}</td>
|
162
162
|
<form id="delete_variant_{{subm_variant._id}}" action="{{ url_for('clinvar.clinvar_delete_object', submission=subm_obj._id, object_type='variant_data') }}" method="POST">
|
@@ -170,7 +170,7 @@
|
|
170
170
|
<ul class="list-group">
|
171
171
|
{% for key, value in variant_header_fields.items() %}
|
172
172
|
{% if subm_variant[key]%}
|
173
|
-
<li class="list-group-item">{{ value }}: <strong>{{ subm_variant[key]
|
173
|
+
<li class="list-group-item">{{ value }}: <strong>{{ subm_variant[key]}}</strong></li>
|
174
174
|
{% endif %}
|
175
175
|
{% endfor %}
|
176
176
|
</ul>
|
@@ -214,7 +214,7 @@
|
|
214
214
|
</form>
|
215
215
|
<td><button id="{{case._id}}" type="button" class="btn btn-primary btn-xs cd_btn"><span class="fa fa-search-plus" aria-hidden="true"></span></button></td>
|
216
216
|
<td><a href="{{ url_for('cases.case', institute_id=institute._id, case_name=subm_obj.cases[case.case_id]) }}" target="_blank" rel="noopener">{{subm_obj.cases[case.case_id]}}</a></td>
|
217
|
-
<td>{{var_key_name[case.linking_id]|truncate(
|
217
|
+
<td>{{var_key_name[case.linking_id]|truncate(100,true,'..')}}</td>
|
218
218
|
<td>{{case.allele_origin}}</td>
|
219
219
|
<form id="delete_casedata_{{case._id}}" action="{{ url_for('clinvar.clinvar_delete_object', submission=subm_obj._id, object_type='case_data') }}" method="POST">
|
220
220
|
<td><button type="submit" name="delete_object" value="{{case._id}}" class="btn btn-danger btn-xs"><span class="fa fa-trash" aria-hidden="true"></span></button></td>
|
@@ -246,8 +246,8 @@
|
|
246
246
|
</div>
|
247
247
|
{% endmacro %}
|
248
248
|
|
249
|
-
{% macro status_modal() %}
|
250
|
-
<div class="modal fade" id="
|
249
|
+
{% macro status_modal(subm_id) %}
|
250
|
+
<div class="modal fade" id="statusModal_{{subm_id}}" tabindex="-1">
|
251
251
|
<div class="modal-dialog">
|
252
252
|
<div class="modal-content">
|
253
253
|
<div class="modal-header">
|
@@ -390,10 +390,10 @@ function validateConditions(){
|
|
390
390
|
return false;
|
391
391
|
}
|
392
392
|
|
393
|
-
// Make sure that provided conditions are numbers, except when condition type is MeSH
|
393
|
+
// Make sure that provided conditions are numbers, except when condition type is MeSH or MedGen
|
394
394
|
var selectedConditionsValues = $('#condition_tags').val();
|
395
395
|
for (let i = 0; i < selectedConditionsValues.length; i++) {
|
396
|
-
if (isNaN(selectedConditionsValues[i]) && $('#condition_type').val()
|
396
|
+
if (isNaN(selectedConditionsValues[i]) && !['Mesh', 'MedGen'].includes($('#condition_type').val())){
|
397
397
|
alert(`Condition ID "${selectedConditionsValues[i]}" has an invalid format.`);
|
398
398
|
return false;
|
399
399
|
}
|
@@ -1,9 +1,13 @@
|
|
1
1
|
import logging
|
2
|
+
from typing import Dict, List, Set
|
2
3
|
|
3
4
|
from flask import flash, redirect, request, url_for
|
4
5
|
from flask_login import current_user
|
6
|
+
from werkzeug.datastructures.structures import ImmutableMultiDict
|
7
|
+
from werkzeug.local import LocalProxy
|
5
8
|
|
6
|
-
from scout.
|
9
|
+
from scout.adapter import MongoAdapter
|
10
|
+
from scout.constants import CASE_TAGS
|
7
11
|
from scout.server.extensions import store
|
8
12
|
from scout.server.utils import user_institutes
|
9
13
|
|
@@ -22,7 +26,7 @@ def institute_select_choices():
|
|
22
26
|
# Collect only institutes available to the user
|
23
27
|
institute_objs = user_institutes(store, current_user)
|
24
28
|
for inst in institute_objs:
|
25
|
-
institute_choices.append((inst["_id"], inst["display_name"]))
|
29
|
+
institute_choices.append((inst["_id"], f'{inst["display_name"]} ({inst["_id"]})'))
|
26
30
|
return institute_choices
|
27
31
|
|
28
32
|
|
@@ -33,116 +37,90 @@ def dashboard_form(request_form=None):
|
|
33
37
|
return form
|
34
38
|
|
35
39
|
|
36
|
-
def
|
37
|
-
"""
|
40
|
+
def populate_dashboard_data(request: LocalProxy) -> dict:
|
41
|
+
"""Collect data to display on the dashboard page"""
|
38
42
|
|
39
|
-
|
40
|
-
search_type(str): example -> "case:"
|
41
|
-
search_term(str): example -> "17867"
|
42
|
-
|
43
|
-
Returns:
|
44
|
-
slice_query(str): example case:17867
|
45
|
-
"""
|
46
|
-
slice_query = None
|
47
|
-
if search_term and search_type:
|
48
|
-
slice_query = "".join([search_type, search_term])
|
49
|
-
|
50
|
-
return slice_query
|
51
|
-
|
52
|
-
|
53
|
-
def populate_dashboard_data(request):
|
54
|
-
"""Prepate data display object to be returned to the view
|
55
|
-
|
56
|
-
Args:
|
57
|
-
request(flask.rquest): request received by the view
|
58
|
-
|
59
|
-
Returns:
|
60
|
-
data(dict): data to be diplayed in the template
|
61
|
-
"""
|
62
|
-
data = {"dashboard_form": dashboard_form(request.form), "search_terms": CASE_SEARCH_TERMS}
|
43
|
+
data = {"dashboard_form": dashboard_form(request.form)}
|
63
44
|
if request.method == "GET":
|
64
45
|
return data
|
65
46
|
|
66
|
-
|
47
|
+
allowed_institutes = [inst[0] for inst in institute_select_choices()]
|
67
48
|
|
68
49
|
institute_id = request.form.get(
|
69
|
-
"search_institute",
|
50
|
+
"search_institute", allowed_institutes[0]
|
70
51
|
) # GET request has no institute, select the first option of the select
|
71
52
|
|
72
|
-
if institute_id and institute_id not in
|
53
|
+
if institute_id and institute_id not in allowed_institutes:
|
73
54
|
flash("Your user is not allowed to visualize this data", "warning")
|
74
55
|
redirect(url_for("dashboard.index"))
|
75
56
|
|
76
57
|
if institute_id == "All":
|
77
58
|
institute_id = None
|
78
59
|
|
79
|
-
|
80
|
-
slice_query = compose_slice_query(request.form.get("search_type"), search_term)
|
81
|
-
get_dashboard_info(store, data, institute_id, slice_query)
|
60
|
+
get_dashboard_info(adapter=store, data=data, institute_id=institute_id, cases_form=request.form)
|
82
61
|
return data
|
83
62
|
|
84
63
|
|
85
|
-
def get_dashboard_info(
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
data(dict): data dictionary to be passed to template
|
90
|
-
institute_id(str): institute id
|
91
|
-
slice_query(str): example case:55888
|
64
|
+
def get_dashboard_info(
|
65
|
+
adapter: MongoAdapter, data: dict = None, institute_id: str = None, cases_form=None
|
66
|
+
) -> dict:
|
67
|
+
"""Append case data stats to data display object"""
|
92
68
|
|
93
|
-
|
94
|
-
data
|
95
|
-
|
96
|
-
#
|
97
|
-
|
98
|
-
|
99
|
-
adapter, institute_id=institute_id, slice_query=slice_query
|
69
|
+
if not data:
|
70
|
+
data = {}
|
71
|
+
|
72
|
+
# Filter data using eventual filter provided in cases filters form
|
73
|
+
filtered_cases_info = get_general_case_info(
|
74
|
+
adapter=adapter, institute_id=institute_id, cases_form=cases_form
|
100
75
|
)
|
101
76
|
|
102
|
-
|
103
|
-
data["total_cases"] =
|
77
|
+
total_filtered_cases = filtered_cases_info["total_cases"]
|
78
|
+
data["total_cases"] = total_filtered_cases
|
104
79
|
|
105
|
-
if
|
80
|
+
if total_filtered_cases == 0:
|
106
81
|
return data
|
107
82
|
|
108
83
|
data["pedigree"] = []
|
109
|
-
for ped_info in
|
110
|
-
ped_info["percent"] = ped_info["count"] /
|
84
|
+
for ped_info in filtered_cases_info["pedigree"].values():
|
85
|
+
ped_info["percent"] = ped_info["count"] / total_filtered_cases
|
111
86
|
data["pedigree"].append(ped_info)
|
112
87
|
|
113
88
|
data["cases"] = get_case_groups(
|
114
|
-
adapter
|
89
|
+
adapter=adapter,
|
90
|
+
total_cases=total_filtered_cases,
|
91
|
+
institute_id=institute_id,
|
92
|
+
name_query=cases_form,
|
115
93
|
)
|
116
94
|
|
117
95
|
data["analysis_types"] = get_analysis_types(
|
118
|
-
adapter,
|
96
|
+
adapter=adapter, institute_id=institute_id, name_query=cases_form
|
119
97
|
)
|
120
98
|
|
121
99
|
overview = [
|
122
100
|
{
|
123
101
|
"title": "Phenotype terms",
|
124
|
-
"count":
|
125
|
-
"percent":
|
102
|
+
"count": filtered_cases_info["phenotype_cases"],
|
103
|
+
"percent": filtered_cases_info["phenotype_cases"] / total_filtered_cases,
|
126
104
|
},
|
127
105
|
{
|
128
106
|
"title": "Causative variants",
|
129
|
-
"count":
|
130
|
-
"percent":
|
107
|
+
"count": filtered_cases_info["causative_cases"],
|
108
|
+
"percent": filtered_cases_info["causative_cases"] / total_filtered_cases,
|
131
109
|
},
|
132
110
|
{
|
133
111
|
"title": "Pinned variants",
|
134
|
-
"count":
|
135
|
-
"percent":
|
112
|
+
"count": filtered_cases_info["pinned_cases"],
|
113
|
+
"percent": filtered_cases_info["pinned_cases"] / total_filtered_cases,
|
136
114
|
},
|
137
115
|
{
|
138
116
|
"title": "Cohort tag",
|
139
|
-
"count":
|
140
|
-
"percent":
|
117
|
+
"count": filtered_cases_info["cohort_cases"],
|
118
|
+
"percent": filtered_cases_info["cohort_cases"] / total_filtered_cases,
|
141
119
|
},
|
142
120
|
{
|
143
121
|
"title": "Case status tag",
|
144
|
-
"count":
|
145
|
-
"percent":
|
122
|
+
"count": filtered_cases_info["tagged_cases"],
|
123
|
+
"percent": filtered_cases_info["tagged_cases"] / total_filtered_cases,
|
146
124
|
},
|
147
125
|
]
|
148
126
|
|
@@ -150,8 +128,8 @@ def get_dashboard_info(adapter, data={}, institute_id=None, slice_query=None):
|
|
150
128
|
overview.append(
|
151
129
|
{
|
152
130
|
"title": CASE_TAGS[stats_tag]["label"] + " status tag",
|
153
|
-
"count":
|
154
|
-
"percent":
|
131
|
+
"count": filtered_cases_info[stats_tag + "_cases"],
|
132
|
+
"percent": filtered_cases_info[stats_tag + "_cases"] / total_filtered_cases,
|
155
133
|
}
|
156
134
|
)
|
157
135
|
|
@@ -160,26 +138,10 @@ def get_dashboard_info(adapter, data={}, institute_id=None, slice_query=None):
|
|
160
138
|
return data
|
161
139
|
|
162
140
|
|
163
|
-
def get_general_case_info(
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
marked or are part of cohorts.
|
168
|
-
|
169
|
-
Gather pedigree information - single, duo, trio or many individuals in case.
|
170
|
-
|
171
|
-
Args:
|
172
|
-
adapter(adapter.MongoAdapter)
|
173
|
-
institute_id(str)
|
174
|
-
slice_query(str): Query to filter cases to obtain statistics for.
|
175
|
-
|
176
|
-
Returns:
|
177
|
-
general(dict)
|
178
|
-
"""
|
179
|
-
general = {}
|
180
|
-
|
181
|
-
# Potentially sensitive slice queries are assumed allowed if we have got this far
|
182
|
-
name_query = slice_query
|
141
|
+
def get_general_case_info(
|
142
|
+
adapter, institute_id: str = None, cases_form: ImmutableMultiDict = None
|
143
|
+
) -> dict:
|
144
|
+
"""Return general information about cases."""
|
183
145
|
|
184
146
|
CASE_GENERAL_INFO_PROJECTION = {
|
185
147
|
"phenotype_terms": 1,
|
@@ -189,77 +151,88 @@ def get_general_case_info(adapter, institute_id=None, slice_query=None):
|
|
189
151
|
"individuals": 1,
|
190
152
|
"tags": 1,
|
191
153
|
}
|
154
|
+
|
192
155
|
cases = adapter.cases(
|
193
|
-
owner=institute_id, name_query=
|
156
|
+
owner=institute_id, name_query=cases_form, projection=CASE_GENERAL_INFO_PROJECTION
|
194
157
|
)
|
195
158
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
]
|
203
|
-
case_counter_keys.extend(CASE_TAGS.keys())
|
159
|
+
# Initialize counters and structures
|
160
|
+
case_counter_keys = ["phenotype", "causative", "pinned", "cohort", "tagged"] + list(
|
161
|
+
CASE_TAGS.keys()
|
162
|
+
)
|
163
|
+
case_counter = initialize_case_counter(case_counter_keys)
|
164
|
+
pedigree = initialize_pedigree()
|
204
165
|
|
205
|
-
|
206
|
-
|
207
|
-
case_counter[counter] = 0
|
166
|
+
case_ids: Set[str] = set()
|
167
|
+
total_cases = 0
|
208
168
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
169
|
+
# Process each case
|
170
|
+
for total_cases, case in enumerate(cases, 1):
|
171
|
+
case_ids.add(case["_id"])
|
172
|
+
update_case_counters(case, case_counter, case_counter_keys)
|
173
|
+
update_pedigree(case, pedigree)
|
174
|
+
|
175
|
+
# Prepare general info dictionary
|
176
|
+
general = {
|
177
|
+
"total_cases": total_cases,
|
178
|
+
"pedigree": pedigree,
|
179
|
+
"case_ids": case_ids,
|
180
|
+
**{f"{key}_cases": count for key, count in case_counter.items()},
|
214
181
|
}
|
215
182
|
|
216
|
-
|
183
|
+
return general
|
217
184
|
|
218
|
-
total_cases = 0
|
219
|
-
for total_cases, case in enumerate(cases, 1):
|
220
|
-
case_ids.add(case["_id"])
|
221
|
-
if case.get("phenotype_terms"):
|
222
|
-
case_counter["phenotype"] += 1
|
223
|
-
if case.get("causatives"):
|
224
|
-
case_counter["causative"] += 1
|
225
|
-
if case.get("suspects"):
|
226
|
-
case_counter["pinned"] += 1
|
227
|
-
if case.get("cohorts"):
|
228
|
-
case_counter["cohort"] += 1
|
229
|
-
if case.get("tags"):
|
230
|
-
case_counter["tagged"] += 1
|
231
|
-
case_tags = case.get("tags")
|
232
|
-
for tag in case_tags:
|
233
|
-
case_counter[tag] += 1
|
234
185
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
if nr_individuals > 3:
|
239
|
-
pedigree["many"]["count"] += 1
|
240
|
-
else:
|
241
|
-
pedigree[nr_individuals]["count"] += 1
|
186
|
+
def initialize_case_counter(case_counter_keys) -> Dict[str, int]:
|
187
|
+
"""Initialize the case counter dictionary with the given keys set to zero."""
|
188
|
+
return {key: 0 for key in case_counter_keys}
|
242
189
|
|
243
|
-
general["total_cases"] = total_cases
|
244
|
-
general["pedigree"] = pedigree
|
245
|
-
general["case_ids"] = case_ids
|
246
|
-
for counter in case_counter_keys:
|
247
|
-
general[counter + "_cases"] = case_counter[counter]
|
248
190
|
|
249
|
-
|
191
|
+
def initialize_pedigree() -> Dict:
|
192
|
+
"""Initialize the pedigree structure with counts set to zero."""
|
193
|
+
return {
|
194
|
+
1: {"title": "Single", "count": 0},
|
195
|
+
2: {"title": "Duo", "count": 0},
|
196
|
+
3: {"title": "Trio", "count": 0},
|
197
|
+
"many": {"title": "Many", "count": 0},
|
198
|
+
}
|
250
199
|
|
251
200
|
|
252
|
-
def
|
253
|
-
"""
|
201
|
+
def update_case_counters(case: dict, case_counter: Dict[str, int], case_counter_keys):
|
202
|
+
"""Update case counter based on the case's attributes."""
|
203
|
+
if case.get("phenotype_terms"):
|
204
|
+
case_counter["phenotype"] += 1
|
205
|
+
if case.get("causatives"):
|
206
|
+
case_counter["causative"] += 1
|
207
|
+
if case.get("suspects"):
|
208
|
+
case_counter["pinned"] += 1
|
209
|
+
if case.get("cohorts"):
|
210
|
+
case_counter["cohort"] += 1
|
211
|
+
if case.get("tags"):
|
212
|
+
case_counter["tagged"] += 1
|
213
|
+
for tag in case.get("tags", []):
|
214
|
+
if tag in case_counter_keys: # Ensure tag is in the predefined keys
|
215
|
+
case_counter[tag] += 1
|
254
216
|
|
255
|
-
Args:
|
256
|
-
store(adapter.MongoAdapter)
|
257
|
-
total_cases(int): Total number of cases
|
258
|
-
slice_query(str): Query to filter cases to obtain statistics for.
|
259
217
|
|
260
|
-
|
261
|
-
|
262
|
-
""
|
218
|
+
def update_pedigree(case: dict, pedigree: Dict):
|
219
|
+
"""Update pedigree information based on the number of individuals in the case."""
|
220
|
+
nr_individuals = len(case.get("individuals", []))
|
221
|
+
if nr_individuals == 0:
|
222
|
+
return
|
223
|
+
if nr_individuals > 3:
|
224
|
+
pedigree["many"]["count"] += 1
|
225
|
+
else:
|
226
|
+
pedigree[nr_individuals]["count"] += 1
|
227
|
+
|
228
|
+
|
229
|
+
def get_case_groups(
|
230
|
+
adapter: MongoAdapter,
|
231
|
+
total_cases: int,
|
232
|
+
institute_id: str = None,
|
233
|
+
name_query: ImmutableMultiDict = None,
|
234
|
+
) -> List[dict]:
|
235
|
+
"""Return the information about case groups"""
|
263
236
|
# Create a group with all cases in the database
|
264
237
|
cases = [{"status": "all", "count": total_cases, "percent": 1}]
|
265
238
|
# Group the cases based on their status
|
@@ -267,12 +240,12 @@ def get_case_groups(adapter, total_cases, institute_id=None, slice_query=None):
|
|
267
240
|
group = {"$group": {"_id": "$status", "count": {"$sum": 1}}}
|
268
241
|
|
269
242
|
subquery = {}
|
270
|
-
if institute_id and
|
271
|
-
subquery = adapter.cases(owner=institute_id, name_query=
|
243
|
+
if institute_id and name_query:
|
244
|
+
subquery = adapter.cases(owner=institute_id, name_query=name_query, yield_query=True)
|
272
245
|
elif institute_id:
|
273
246
|
subquery = adapter.cases(owner=institute_id, yield_query=True)
|
274
|
-
elif
|
275
|
-
subquery = adapter.cases(name_query=
|
247
|
+
elif name_query:
|
248
|
+
subquery = adapter.cases(name_query=name_query, yield_query=True)
|
276
249
|
|
277
250
|
query = {"$match": subquery} if subquery else {}
|
278
251
|
|
@@ -294,28 +267,18 @@ def get_case_groups(adapter, total_cases, institute_id=None, slice_query=None):
|
|
294
267
|
return cases
|
295
268
|
|
296
269
|
|
297
|
-
def get_analysis_types(
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
adapter(adapter.MongoAdapter)
|
302
|
-
total_cases(int): Total number of cases
|
303
|
-
institute_id(str)
|
304
|
-
slice_query(str): Query to filter cases to obtain statistics for.
|
305
|
-
Returns:
|
306
|
-
analysis_types array of hashes with name: analysis_type(str), count: count(int)
|
307
|
-
|
308
|
-
"""
|
309
|
-
# Group cases based on analysis type of the individuals
|
310
|
-
query = {}
|
270
|
+
def get_analysis_types(
|
271
|
+
adapter: MongoAdapter, institute_id: str = None, name_query: ImmutableMultiDict = None
|
272
|
+
) -> List[dict]:
|
273
|
+
"""Group cases based on analysis type of the individuals."""
|
311
274
|
|
312
275
|
subquery = {}
|
313
|
-
if institute_id and
|
314
|
-
subquery = adapter.cases(owner=institute_id, name_query=
|
276
|
+
if institute_id and name_query:
|
277
|
+
subquery = adapter.cases(owner=institute_id, name_query=name_query, yield_query=True)
|
315
278
|
elif institute_id:
|
316
279
|
subquery = adapter.cases(owner=institute_id, yield_query=True)
|
317
|
-
elif
|
318
|
-
subquery = adapter.cases(name_query=
|
280
|
+
elif name_query:
|
281
|
+
subquery = adapter.cases(name_query=name_query, yield_query=True)
|
319
282
|
|
320
283
|
query = {"$match": subquery}
|
321
284
|
|
@@ -1,12 +1,7 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
|
-
from flask_wtf import FlaskForm
|
3
2
|
from wtforms import SelectField, StringField, SubmitField, validators
|
4
3
|
|
5
|
-
from scout.
|
6
|
-
|
7
|
-
CASE_SEARCH_KEY = [("", "")] + [
|
8
|
-
(value["prefix"], value["label"]) for key, value in CASE_SEARCH_TERMS.items()
|
9
|
-
]
|
4
|
+
from scout.server.blueprints.institutes.forms import CaseFilterForm
|
10
5
|
|
11
6
|
|
12
7
|
class NonValidatingSelectField(SelectField):
|
@@ -16,12 +11,7 @@ class NonValidatingSelectField(SelectField):
|
|
16
11
|
pass
|
17
12
|
|
18
13
|
|
19
|
-
class DashboardFilterForm(
|
20
|
-
"""Takes care of cases filtering
|
14
|
+
class DashboardFilterForm(CaseFilterForm):
|
15
|
+
"""Takes care of cases filtering on dashboard page"""
|
21
16
|
|
22
|
-
search_type = NonValidatingSelectField(
|
23
|
-
"Search by", [validators.Optional()], choices=CASE_SEARCH_KEY
|
24
|
-
)
|
25
17
|
search_institute = NonValidatingSelectField("Institute", choices=[])
|
26
|
-
search_term = StringField("Search term")
|
27
|
-
search = SubmitField(label="Search")
|