scout-browser 4.95.0__py3-none-any.whl → 4.97.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 (99) hide show
  1. scout/adapter/mongo/case.py +75 -70
  2. scout/adapter/mongo/filter.py +28 -11
  3. scout/adapter/mongo/institute.py +2 -0
  4. scout/adapter/mongo/omics_variant.py +20 -5
  5. scout/adapter/mongo/query.py +104 -95
  6. scout/adapter/mongo/variant.py +0 -5
  7. scout/adapter/mongo/variant_loader.py +10 -12
  8. scout/build/case.py +3 -1
  9. scout/build/individual.py +3 -11
  10. scout/commands/delete/delete_command.py +87 -49
  11. scout/commands/load/research.py +4 -4
  12. scout/commands/load/variants.py +25 -8
  13. scout/commands/setup/setup_scout.py +1 -1
  14. scout/commands/update/case.py +12 -0
  15. scout/commands/update/individual.py +1 -2
  16. scout/constants/__init__.py +7 -2
  17. scout/constants/acmg.py +25 -18
  18. scout/constants/file_types.py +68 -119
  19. scout/constants/filters.py +2 -1
  20. scout/constants/gene_tags.py +3 -3
  21. scout/constants/igv_tracks.py +7 -11
  22. scout/constants/query_terms.py +2 -2
  23. scout/demo/643594.config.yaml +6 -0
  24. scout/demo/643594.peddy.ped +1 -1
  25. scout/demo/643594.somalier.ancestry.tsv +4 -0
  26. scout/demo/643594.somalier.pairs.tsv +4 -0
  27. scout/demo/643594.somalier.samples.tsv +4 -0
  28. scout/demo/cancer.load_config.yaml +2 -3
  29. scout/demo/resources/__init__.py +1 -1
  30. scout/demo/resources/gnomad.v4.1.constraint_metrics_reduced.tsv +3755 -0
  31. scout/demo/rnafusion.load_config.yaml +1 -0
  32. scout/exceptions/database.py +1 -1
  33. scout/load/all.py +8 -16
  34. scout/models/case/case.py +1 -0
  35. scout/models/case/case_loading_models.py +15 -5
  36. scout/models/managed_variant.py +3 -3
  37. scout/models/omics_variant.py +3 -3
  38. scout/parse/case.py +113 -5
  39. scout/parse/pedqc.py +127 -0
  40. scout/parse/variant/frequency.py +9 -6
  41. scout/parse/variant/variant.py +71 -39
  42. scout/server/app.py +14 -0
  43. scout/server/blueprints/alignviewers/controllers.py +2 -0
  44. scout/server/blueprints/alignviewers/templates/alignviewers/igv_viewer.html +3 -0
  45. scout/server/blueprints/alignviewers/templates/alignviewers/utils.html +1 -1
  46. scout/server/blueprints/cases/controllers.py +25 -3
  47. scout/server/blueprints/cases/templates/cases/case.html +3 -0
  48. scout/server/blueprints/cases/templates/cases/case_report.html +28 -2
  49. scout/server/blueprints/cases/templates/cases/chanjo2_form.html +2 -2
  50. scout/server/blueprints/cases/templates/cases/collapsible_actionbar.html +12 -0
  51. scout/server/blueprints/cases/templates/cases/gene_panel.html +9 -3
  52. scout/server/blueprints/cases/templates/cases/individuals_table.html +4 -1
  53. scout/server/blueprints/cases/templates/cases/utils.html +23 -19
  54. scout/server/blueprints/cases/views.py +5 -9
  55. scout/server/blueprints/clinvar/controllers.py +12 -11
  56. scout/server/blueprints/clinvar/templates/clinvar/clinvar_submissions.html +10 -14
  57. scout/server/blueprints/clinvar/templates/clinvar/multistep_add_variant.html +15 -7
  58. scout/server/blueprints/clinvar/views.py +18 -31
  59. scout/server/blueprints/institutes/controllers.py +20 -1
  60. scout/server/blueprints/institutes/forms.py +5 -1
  61. scout/server/blueprints/institutes/templates/overview/institute_settings.html +7 -0
  62. scout/server/blueprints/institutes/templates/overview/utils.html +20 -1
  63. scout/server/blueprints/omics_variants/templates/omics_variants/outliers.html +9 -2
  64. scout/server/blueprints/omics_variants/views.py +8 -10
  65. scout/server/blueprints/variant/controllers.py +30 -1
  66. scout/server/blueprints/variant/templates/variant/cancer-variant.html +21 -5
  67. scout/server/blueprints/variant/templates/variant/components.html +26 -9
  68. scout/server/blueprints/variant/templates/variant/variant.html +4 -2
  69. scout/server/blueprints/variant/templates/variant/variant_details.html +1 -1
  70. scout/server/blueprints/variant/utils.py +2 -0
  71. scout/server/blueprints/variant/views.py +10 -3
  72. scout/server/blueprints/variants/controllers.py +29 -3
  73. scout/server/blueprints/variants/forms.py +37 -10
  74. scout/server/blueprints/variants/templates/variants/cancer-variants.html +5 -4
  75. scout/server/blueprints/variants/templates/variants/components.html +12 -10
  76. scout/server/blueprints/variants/templates/variants/str-variants.html +13 -9
  77. scout/server/blueprints/variants/templates/variants/utils.html +59 -36
  78. scout/server/blueprints/variants/views.py +45 -60
  79. scout/server/extensions/beacon_extension.py +1 -1
  80. scout/server/extensions/bionano_extension.py +5 -5
  81. scout/server/extensions/chanjo2_extension.py +40 -1
  82. scout/server/extensions/chanjo_extension.py +1 -1
  83. scout/server/extensions/clinvar_extension.py +56 -2
  84. scout/server/extensions/matchmaker_extension.py +1 -1
  85. scout/server/links.py +0 -14
  86. scout/server/static/bs_styles.css +2 -0
  87. scout/server/templates/layout.html +1 -0
  88. scout/server/utils.py +5 -0
  89. scout/utils/acmg.py +5 -5
  90. scout/utils/ensembl_biomart_clients.py +2 -11
  91. scout/utils/scout_requests.py +1 -1
  92. {scout_browser-4.95.0.dist-info → scout_browser-4.97.0.dist-info}/METADATA +1 -1
  93. {scout_browser-4.95.0.dist-info → scout_browser-4.97.0.dist-info}/RECORD +96 -94
  94. scout/demo/resources/gnomad.v4.0.constraint_metrics_reduced.tsv +0 -3755
  95. scout/parse/peddy.py +0 -149
  96. scout/utils/sort.py +0 -21
  97. {scout_browser-4.95.0.dist-info → scout_browser-4.97.0.dist-info}/WHEEL +0 -0
  98. {scout_browser-4.95.0.dist-info → scout_browser-4.97.0.dist-info}/entry_points.txt +0 -0
  99. {scout_browser-4.95.0.dist-info → scout_browser-4.97.0.dist-info}/licenses/LICENSE +0 -0
@@ -3,7 +3,7 @@
3
3
  {% from "variant/buttons.html" import reviewer_button%}
4
4
  {% from "variant/gene_disease_relations.html" import inheritance_badge %}
5
5
  {% from "variants/components.html" import external_scripts, external_stylesheets, frequency_cell_general %}
6
- {% from "variants/utils.html" import cell_rank, dismiss_variants_block, filter_form_footer, update_stash_filter_button_status, pagination_footer, pagination_hidden_div, str_filters, filter_script_main %}
6
+ {% from "variants/utils.html" import callers_cell, cell_rank, dismiss_variants_block, filter_form_footer, update_stash_filter_button_status, pagination_footer, pagination_hidden_div, str_filters, filter_script_main %}
7
7
 
8
8
 
9
9
  {% block title %}
@@ -60,13 +60,14 @@
60
60
  <thead class="thead table-light">
61
61
  <tr>
62
62
  <th style="width:8%" title="Index">Index</th>
63
- <th title="Repeat ID">Repeat locus</th>
64
- <th title="Repeat unit">Reference repeat unit</th>
65
- <th title="ALT">Estimated size</th>
66
- <th title="ReferenceSize">Reference size</th>
67
- <th title="Status">Status</th>
63
+ <th title="Repeat locus ID">Repeat locus</th>
64
+ <th style="width:8%" title="Reference repeat unit">Ref RU</th>
65
+ <th style="width:6%" title="ALT">Est size</th>
66
+ <th style="width:6%" title="ReferenceSize">Ref size</th>
67
+ <th style="width:8%" title="Caller and call filter status">Qual</th>
68
+ <th style="width:8%" title="Status">Status</th>
68
69
  <th title="GT">Genotype</th>
69
- <th title="Chromosome" style="width:6%">Chr.</th>
70
+ <th style="width:6%" title="Chromosome" style="width:6%">Chr.</th>
70
71
  <th title="Position" style="width:20%">Position</th>
71
72
  </tr>
72
73
  </thead>
@@ -95,11 +96,14 @@
95
96
  <td class="text-end">{{ variant.str_display_ru or variant.str_ru or variant.reference }}</td>
96
97
  <td class="text-end"><b><span data-bs-toggle="tooltip" title="{{ variant.alternative }}">{{ variant.str_mc }}</span></b></td>
97
98
  <td class="text-end"><span data-bs-toggle="tooltip" title="{{ variant.reference }}">{{ variant.str_ref or "." }}</span></td>
99
+ <td>{{ callers_cell(variant) }}</td>
98
100
  <td>{{ str_status(variant) }}</td>
99
101
  <td>{% for sample in variant.samples %}
100
102
  {% if sample.genotype_call != "./." %}
101
- <div class="float-start">{{ sample.display_name }}</div>
102
- <div class="float-end">{{ sample.genotype_call }}</div><br>
103
+ <div class="row">
104
+ <div class="col-8">{{ sample.display_name }}</div>
105
+ <div class="col-4 text-end">{{ sample.genotype_call }}</div>
106
+ </div>
103
107
  {% endif %}
104
108
  {% endfor %}
105
109
  </td>
@@ -36,12 +36,18 @@
36
36
  the_form.submit();
37
37
  }}
38
38
 
39
- var show_unaffected =document.getElementById('show_unaffected');
39
+ var show_unaffected = document.getElementById('show_unaffected');
40
40
  if (show_unaffected) {
41
41
  show_unaffected.onchange = function() {
42
42
  the_form.submit();
43
43
  }}
44
44
 
45
+ var show_soft_filtered = document.getElementById('show_soft_filtered');
46
+ if (show_soft_filtered) {
47
+ show_soft_filtered.onchange = function() {
48
+ the_form.submit();
49
+ }}
50
+
45
51
  function resetPage(){
46
52
  document.getElementById('page').value = "1";
47
53
  }
@@ -102,6 +108,18 @@
102
108
  {{ form.hide_dismissed(class="form-check-input") }}
103
109
  {{ form.hide_dismissed.label(class="form-check-label ms-2") }}
104
110
  </div>
111
+ {% if institute.soft_filters %} <!-- Available only for institutes with soft filters in settings -->
112
+ <div class="form-check d-flex justify-content-start">
113
+ {{ form.show_soft_filtered(class="form-check-input") }}
114
+ {{ form.show_soft_filtered.label(
115
+ class="form-check-label ms-2",
116
+ data_bs_toggle="tooltip",
117
+ title="Filters are defined by an admin in the institute settings. Current filters are: " ~
118
+ form.institute_soft_filters.data|safe
119
+ ) }}
120
+ {{ form.institute_soft_filters() }}
121
+ </div>
122
+ {% endif %}
105
123
  <div class="form-check d-flex justify-content-start">
106
124
  {% if institute.check_show_all_vars %}
107
125
  <input type="checkbox" class="form-check-input" name="show_unaffected" id="show_unaffected" checked disabled>
@@ -275,6 +293,16 @@
275
293
  </div>
276
294
  {% endmacro %}
277
295
 
296
+ {% macro variant_size_filter(form) %}
297
+ <div class="col-2">
298
+ {{ form.size_selector.label(class="control-label") }}
299
+ <div class="input-group">
300
+ {{ form.size_selector(class="form-select") }}
301
+ {{ form.size(class="form-control") }}
302
+ </div>
303
+ </div>
304
+ {% endmacro %}
305
+
278
306
  {% macro snv_filters(form, institute, case, filters)%}
279
307
  <input type="hidden" name="variant_type" value="{{ form.variant_type.data }}">
280
308
  {{ variants_common_filters(form, "snv") }}
@@ -308,16 +336,28 @@
308
336
  {{ form.spidex_human(class="selectpicker", data_style="btn-secondary") }}
309
337
  </div>
310
338
  <div class="col-2">
311
- {{ form.clinsig.label(class="control-label") }}
339
+ <span>{{ form.clinsig.label(class="control-label") }}</span>
340
+ <span style="float:right;">{{ form.clinsig_exclude.label(class="control-label", data_bs_toggle="tooltip", data_bs_placement="left", title="Exclude variants with clinical significance among the selected categories.") }} {{form.clinsig_exclude}}</span>
312
341
  {{ form.clinsig(class="selectpicker", data_style="btn-secondary") }}
313
342
  </div>
314
- <div class="col-1">
315
- {{ form.clinsig_confident_always_returned.label(class="control-label", data_bs_toggle="tooltip", data_bs_placement="top", title="Always show selected CLINSIG entries with trusted revision status levels.") }}
316
- <div>{{ form.clinsig_confident_always_returned() }}</div>
317
- </div>
318
- <div class="col-1">
319
- {{ form.clinvar_tag() }}
320
- {{ form.clinvar_tag.label(class="ms-2") }}
343
+ <div class="col-2">
344
+ <div class="row">
345
+ <div class="class="form-check">
346
+ {{ form.clinvar_trusted_revstat(class="form-check-input") }}
347
+ {{ form.clinvar_trusted_revstat.label(class="form-check-label", data_bs_toggle="tooltip", data_bs_placement="top", title="Limit search to variants with trusted ClinVar revision status levels: mult, multiple_submitters, single, single_submitter, exp, reviewed_by_expert_panel, guideline, practice_guideline.") }}
348
+ </div>
349
+ </div>
350
+ <div class="row">
351
+ <div class="class="form-check">
352
+ {{ form.clinvar_tag(class="form-check-input") }}
353
+ {{ form.clinvar_tag.label(class="form-check-label", data_bs_toggle="tooltip", data_bs_placement="top", title="Return only variants with annotated ClinVar significance.") }}
354
+ </div>
355
+ </div>
356
+ <div class="row">
357
+ <div class="class="form-check">
358
+ {{ form.prioritise_clinvar(class="form-check-input") }}
359
+ {{ form.prioritise_clinvar.label(class="form-check-label", data_bs_toggle="tooltip", data_bs_placement="top", title="Include variants matching the selected ClinVar conditions, in addition to those found using the other search criteria (broadens the search). Note that variants excluded using ClinVar tags will still be returned when found using the other search criteria.") }}
360
+ </div>
321
361
  </div>
322
362
  </div>
323
363
  <div class="row mb-2">
@@ -336,10 +376,10 @@
336
376
  </div>
337
377
  </div>
338
378
  </div>
339
- <div class="col-2">
379
+ <div class="col-1">
340
380
  {{ wtf.form_field(form.start) }}
341
381
  </div>
342
- <div class="col-2">
382
+ <div class="col-1">
343
383
  {{ wtf.form_field(form.end) }}
344
384
  </div>
345
385
  <div class="col-2">
@@ -348,6 +388,7 @@
348
388
  <div class="col-2">
349
389
  {{ wtf.form_field(form.cytoband_end) }}
350
390
  </div>
391
+ {{ variant_size_filter(form) }}
351
392
  </div>
352
393
  <div class="row">
353
394
  <div class="col-2">
@@ -577,16 +618,6 @@
577
618
  gene_panels=['hpo']) }}">HPO gene list</a>
578
619
  </div>
579
620
  </div>
580
- <div class="col-2">
581
- {{ form.size.label(class="control-label") }}
582
- {{ form.size(class="form-control", type="number") }}
583
- </div>
584
- <div class="col-2 d-flex align-items-end">
585
- <div class="mb-2 form-check">
586
- {{ form.size_shorter.label(class="form-check-label") }}
587
- {{ form.size_shorter(class="form-check-input",type="checkbox") }}
588
- </div>
589
- </div>
590
621
  <div class="col-1 d-flex align-items-end">
591
622
  <div class="mb-2 form-check">
592
623
  {{ form.decipher.label(class="form-check-label") }}
@@ -622,10 +653,10 @@
622
653
  </div>
623
654
  </div>
624
655
  </div>
625
- <div class="col-2">
656
+ <div class="col-1">
626
657
  {{ wtf.form_field(form.start) }}
627
658
  </div>
628
- <div class="col-2">
659
+ <div class="col-1">
629
660
  {{ wtf.form_field(form.end) }}
630
661
  </div>
631
662
  <div class="col-1">
@@ -634,6 +665,7 @@
634
665
  <div class="col-1">
635
666
  {{ wtf.form_field(form.cytoband_end) }}
636
667
  </div>
668
+ {{ variant_size_filter(form) }}
637
669
  </div>
638
670
  <div class="row mb-3">
639
671
  <div class="col-2">
@@ -694,7 +726,8 @@
694
726
  {{ form.genetic_models(class="selectpicker", data_style="btn-secondary") }}
695
727
  </div>
696
728
  <div class="col-2">
697
- {{ form.clinsig.label(class="control-label") }}
729
+ <span>{{ form.clinsig.label(class="control-label") }}</span>
730
+ <span style="float:right;">{{ form.clinsig_exclude.label(class="control-label", data_bs_toggle="tooltip", data_bs_placement="left", title="Exclude variants with clinical significance among the selected categories.") }} {{form.clinsig_exclude}}</span>
698
731
  {{ form.clinsig(class="selectpicker", data_style="btn-secondary") }}
699
732
  </div>
700
733
  <div class="col-2">
@@ -765,9 +798,9 @@
765
798
  <div class="col">
766
799
  {{ wtf.form_field(form.cytoband_end) }}
767
800
  </div>
801
+ {{ variant_size_filter(form) }}
768
802
  </div>
769
803
  <div class="row" style="margin-top:10px;">
770
- <div class="col-2"></div>
771
804
  <div class="col-2">
772
805
  <a class="btn btn-secondary btn-sm form-control" data-bs-toggle="collapse" href="#stringcoords" aria-expanded="false" aria-controls="stringcoords">
773
806
  String coordinates
@@ -841,16 +874,6 @@
841
874
  {{ form.hgnc_symbols.label(class="control-label") }}
842
875
  {{ form.hgnc_symbols(class="form-control") }}
843
876
  </div>
844
- <div class="col-2">
845
- {{ form.size.label(class="control-label") }}
846
- {{ form.size(class="form-control", type="number") }}
847
- </div>
848
- <div class="col-2 d-flex align-items-end">
849
- <div class="form-check mb-2">
850
- {{ form.size_shorter.label(class="form-check-label") }}
851
- {{ form.size_shorter(class="form-check-input",type="checkbox") }}
852
- </div>
853
- </div>
854
877
  <div class="col-1">
855
878
  </div>
856
879
  <div class="col-1">
@@ -889,9 +912,9 @@
889
912
  <div class="col">
890
913
  {{ wtf.form_field(form.cytoband_end) }}
891
914
  </div>
915
+ {{ variant_size_filter(form) }}
892
916
  </div>
893
917
  <div class="row" style="margin-top:10px;">
894
- <div class="col-2"></div>
895
918
  <div class="col-2">
896
919
  <a class="btn btn-secondary btn-sm form-control" data-bs-toggle="collapse" href="#stringcoords" aria-expanded="false" aria-controls="stringcoords">
897
920
  String coordinates
@@ -67,8 +67,7 @@ def variants(institute_id, case_name):
67
67
 
68
68
  variants_stats = store.case_variants_count(case_obj["_id"], institute_id, variant_type, False)
69
69
 
70
- if request.form.get("hpo_clinical_filter"):
71
- case_obj["hpo_clinical_filter"] = True
70
+ controllers.set_hpo_clinical_filter(case_obj, request.form)
72
71
 
73
72
  user_obj = store.user(current_user.email)
74
73
  if request.method == "POST":
@@ -97,14 +96,12 @@ def variants(institute_id, case_name):
97
96
 
98
97
  controllers.populate_force_show_unaffected_vars(institute_obj, form)
99
98
 
100
- # populate filters dropdown
101
- available_filters = list(store.filters(institute_id, category))
102
- form.filters.choices = [
103
- (filter.get("_id"), filter.get("display_name")) for filter in available_filters
104
- ]
105
99
  # Populate chromosome select choices
106
100
  controllers.populate_chrom_choices(form, case_obj)
107
101
 
102
+ # Populate custom soft filters
103
+ controllers.populate_institute_soft_filters(form=form, institute_obj=institute_obj)
104
+
108
105
  # populate available panel choices
109
106
  form.gene_panels.choices = controllers.gene_panel_choices(store, institute_obj, case_obj)
110
107
 
@@ -161,7 +158,9 @@ def variants(institute_id, case_name):
161
158
  cytobands=cytobands,
162
159
  dismiss_variant_options=DISMISS_VARIANT_OPTIONS,
163
160
  expand_search=controllers.get_expand_search(request.form),
164
- filters=available_filters,
161
+ filters=controllers.populate_persistent_filters_choices(
162
+ institute_id=institute_id, category=category, form=form
163
+ ),
165
164
  form=form,
166
165
  genetic_models_palette=GENETIC_MODELS_PALETTE,
167
166
  inherit_palette=INHERITANCE_PALETTE,
@@ -210,15 +209,12 @@ def str_variants(institute_id, case_name):
210
209
  controllers.populate_force_show_unaffected_vars(institute_obj, form)
211
210
  controllers.update_form_hgnc_symbols(store, case_obj, form)
212
211
 
213
- # populate filters dropdown
214
- available_filters = list(store.filters(institute_id, category))
215
- form.filters.choices = [
216
- (filter.get("_id"), filter.get("display_name")) for filter in available_filters
217
- ]
218
-
219
212
  # Populate chromosome select choices
220
213
  controllers.populate_chrom_choices(form, case_obj)
221
214
 
215
+ # Populate custom soft filters
216
+ controllers.populate_institute_soft_filters(form=form, institute_obj=institute_obj)
217
+
222
218
  # populate available panel choices
223
219
  form.gene_panels.choices = controllers.gene_panel_choices(store, institute_obj, case_obj)
224
220
 
@@ -254,7 +250,9 @@ def str_variants(institute_id, case_name):
254
250
  cytobands=cytobands,
255
251
  dismiss_variant_options=DISMISS_VARIANT_OPTIONS,
256
252
  expand_search=controllers.get_expand_search(request.form),
257
- filters=available_filters,
253
+ filters=controllers.populate_persistent_filters_choices(
254
+ institute_id=institute_id, category=category, form=form
255
+ ),
258
256
  form=form,
259
257
  inherit_palette=INHERITANCE_PALETTE,
260
258
  institute=institute_obj,
@@ -282,8 +280,7 @@ def sv_variants(institute_id, case_name):
282
280
  variant_type = "clinical"
283
281
  variants_stats = store.case_variants_count(case_obj["_id"], institute_id, variant_type, False)
284
282
 
285
- if request.form.get("hpo_clinical_filter"):
286
- case_obj["hpo_clinical_filter"] = True
283
+ controllers.set_hpo_clinical_filter(case_obj, request.form)
287
284
 
288
285
  if "dismiss_submit" in request.form: # dismiss a list of variants
289
286
  controllers.dismiss_variant_list(
@@ -299,15 +296,12 @@ def sv_variants(institute_id, case_name):
299
296
  controllers.activate_case(store, institute_obj, case_obj, current_user)
300
297
  form = controllers.populate_sv_filters_form(store, institute_obj, case_obj, category, request)
301
298
 
302
- # populate filters dropdown
303
- available_filters = list(store.filters(institute_obj["_id"], category))
304
- form.filters.choices = [
305
- (filter.get("_id"), filter.get("display_name")) for filter in available_filters
306
- ]
307
-
308
299
  # Populate chromosome select choices
309
300
  controllers.populate_chrom_choices(form, case_obj)
310
301
 
302
+ # Populate custom soft filters
303
+ controllers.populate_institute_soft_filters(form=form, institute_obj=institute_obj)
304
+
311
305
  genome_build = "38" if "38" in str(case_obj.get("genome_build", "37")) else "37"
312
306
  cytobands = store.cytoband_by_chrom(genome_build)
313
307
 
@@ -334,7 +328,9 @@ def sv_variants(institute_id, case_name):
334
328
  cytobands=cytobands,
335
329
  dismiss_variant_options=DISMISS_VARIANT_OPTIONS,
336
330
  expand_search=controllers.get_expand_search(request.form),
337
- filters=available_filters,
331
+ filters=controllers.populate_persistent_filters_choices(
332
+ institute_id=institute_id, category=category, form=form
333
+ ),
338
334
  form=form,
339
335
  inherit_palette=INHERITANCE_PALETTE,
340
336
  institute=institute_obj,
@@ -361,8 +357,7 @@ def mei_variants(institute_id, case_name):
361
357
  )
362
358
  variants_stats = store.case_variants_count(case_obj["_id"], institute_id, variant_type, False)
363
359
 
364
- if request.form.get("hpo_clinical_filter"):
365
- case_obj["hpo_clinical_filter"] = True
360
+ controllers.set_hpo_clinical_filter(case_obj, request.form)
366
361
 
367
362
  if "dismiss_submit" in request.form: # dismiss a list of variants
368
363
  controllers.dismiss_variant_list(
@@ -393,15 +388,12 @@ def mei_variants(institute_id, case_name):
393
388
  # set chromosome to all chromosomes
394
389
  form.chrom.data = request.args.get("chrom", "")
395
390
 
396
- # populate filters dropdown
397
- available_filters = list(store.filters(institute_obj["_id"], category))
398
- form.filters.choices = [
399
- (filter.get("_id"), filter.get("display_name")) for filter in available_filters
400
- ]
401
-
402
391
  # Populate chromosome select choices
403
392
  controllers.populate_chrom_choices(form, case_obj)
404
393
 
394
+ # Populate custom soft filters
395
+ controllers.populate_institute_soft_filters(form=form, institute_obj=institute_obj)
396
+
405
397
  # populate available panel choices
406
398
  form.gene_panels.choices = controllers.gene_panel_choices(store, institute_obj, case_obj)
407
399
 
@@ -431,7 +423,9 @@ def mei_variants(institute_id, case_name):
431
423
  cytobands=cytobands,
432
424
  dismiss_variant_options=DISMISS_VARIANT_OPTIONS,
433
425
  expand_search=controllers.get_expand_search(request.form),
434
- filters=available_filters,
426
+ filters=controllers.populate_persistent_filters_choices(
427
+ institute_id=institute_id, category=category, form=form
428
+ ),
435
429
  form=form,
436
430
  inherit_palette=INHERITANCE_PALETTE,
437
431
  institute=institute_obj,
@@ -507,15 +501,12 @@ def cancer_variants(institute_id, case_name):
507
501
  # update status of case if visited for the first time
508
502
  controllers.activate_case(store, institute_obj, case_obj, current_user)
509
503
 
510
- # populate filters dropdown
511
- available_filters = list(store.filters(institute_id, category))
512
- form.filters.choices = [
513
- (filter.get("_id"), filter.get("display_name")) for filter in available_filters
514
- ]
515
-
516
504
  # Populate chromosome select choices
517
505
  controllers.populate_chrom_choices(form, case_obj)
518
506
 
507
+ # Populate custom soft filters
508
+ controllers.populate_institute_soft_filters(form=form, institute_obj=institute_obj)
509
+
519
510
  form.gene_panels.choices = controllers.gene_panel_choices(store, institute_obj, case_obj)
520
511
 
521
512
  genome_build = "38" if "38" in str(case_obj.get("genome_build", "37")) else "37"
@@ -550,7 +541,9 @@ def cancer_variants(institute_id, case_name):
550
541
  **CANCER_SPECIFIC_VARIANT_DISMISS_OPTIONS,
551
542
  },
552
543
  expand_search=controllers.get_expand_search(request.form),
553
- filters=available_filters,
544
+ filters=controllers.populate_persistent_filters_choices(
545
+ institute_id=institute_id, category=category, form=form
546
+ ),
554
547
  result_size=result_size,
555
548
  show_dismiss_block=controllers.get_show_dismiss_block(),
556
549
  total_variants=variants_stats.get(variant_type, {}).get(category, "NA"),
@@ -574,9 +567,6 @@ def cancer_sv_variants(institute_id, case_name):
574
567
  variant_type = "clinical"
575
568
  variants_stats = store.case_variants_count(case_obj["_id"], institute_id, variant_type, False)
576
569
 
577
- if request.form.get("hpo_clinical_filter"):
578
- case_obj["hpo_clinical_filter"] = True
579
-
580
570
  if "dismiss_submit" in request.form: # dismiss a list of variants
581
571
  controllers.dismiss_variant_list(
582
572
  store,
@@ -591,15 +581,12 @@ def cancer_sv_variants(institute_id, case_name):
591
581
  controllers.activate_case(store, institute_obj, case_obj, current_user)
592
582
  form = controllers.populate_sv_filters_form(store, institute_obj, case_obj, category, request)
593
583
 
594
- # populate filters dropdown
595
- available_filters = list(store.filters(institute_obj["_id"], category))
596
- form.filters.choices = [
597
- (filter.get("_id"), filter.get("display_name")) for filter in available_filters
598
- ]
599
-
600
584
  # Populate chromosome select choices
601
585
  controllers.populate_chrom_choices(form, case_obj)
602
586
 
587
+ # Populate custom soft filters
588
+ controllers.populate_institute_soft_filters(form=form, institute_obj=institute_obj)
589
+
603
590
  genome_build = "38" if "38" in str(case_obj.get("genome_build", "37")) else "37"
604
591
  cytobands = store.cytoband_by_chrom(genome_build)
605
592
 
@@ -630,7 +617,9 @@ def cancer_sv_variants(institute_id, case_name):
630
617
  **CANCER_SPECIFIC_VARIANT_DISMISS_OPTIONS,
631
618
  },
632
619
  expand_search=controllers.get_expand_search(request.form),
633
- filters=available_filters,
620
+ filters=controllers.populate_persistent_filters_choices(
621
+ institute_id=institute_id, category=category, form=form
622
+ ),
634
623
  form=form,
635
624
  inherit_palette=INHERITANCE_PALETTE,
636
625
  institute=institute_obj,
@@ -660,9 +649,6 @@ def fusion_variants(institute_id, case_name):
660
649
  variant_type = "clinical"
661
650
  variants_stats = store.case_variants_count(case_obj["_id"], institute_id, variant_type, False)
662
651
 
663
- if request.form.get("hpo_clinical_filter"):
664
- case_obj["hpo_clinical_filter"] = True
665
-
666
652
  if "dismiss_submit" in request.form: # dismiss a list of variants
667
653
  controllers.dismiss_variant_list(
668
654
  store,
@@ -679,15 +665,12 @@ def fusion_variants(institute_id, case_name):
679
665
  store, institute_obj, case_obj, category, request
680
666
  )
681
667
 
682
- # populate filters dropdown
683
- available_filters = list(store.filters(institute_obj["_id"], category))
684
- form.filters.choices = [
685
- (filter.get("_id"), filter.get("display_name")) for filter in available_filters
686
- ]
687
-
688
668
  # Populate chromosome select choices
689
669
  controllers.populate_chrom_choices(form, case_obj)
690
670
 
671
+ # Populate custom soft filters
672
+ controllers.populate_institute_soft_filters(form=form, institute_obj=institute_obj)
673
+
691
674
  genome_build = "38" if "38" in str(case_obj.get("genome_build", "37")) else "37"
692
675
  cytobands = store.cytoband_by_chrom(genome_build)
693
676
 
@@ -717,7 +700,9 @@ def fusion_variants(institute_id, case_name):
717
700
  **DISMISS_VARIANT_OPTIONS,
718
701
  },
719
702
  expand_search=controllers.get_expand_search(request.form),
720
- filters=available_filters,
703
+ filters=controllers.populate_persistent_filters_choices(
704
+ institute_id=institute_id, category=category, form=form
705
+ ),
721
706
  form=form,
722
707
  institute=institute_obj,
723
708
  manual_rank_options=MANUAL_RANK_OPTIONS,
@@ -1,5 +1,5 @@
1
1
  """Scout supports integration with the Clinical Genomics SciLifeLab Beacon
2
- cgbeacon2: https://github.com/Clinical-Genomics/cgbeacon2
2
+ cgbeacon2: https://github.com/Clinical-Genomics/cgbeacon2
3
3
  """
4
4
 
5
5
  import datetime
@@ -1,10 +1,10 @@
1
1
  """
2
- Connect to BioNano Access server via its API.
2
+ Connect to BioNano Access server via its API.
3
3
 
4
- The server API we connect to is described in the following document:
5
- https://bionano.com/wp-content/uploads/2023/01/30462-Bionano-Access-API-Guide-1.pdf
6
- For further development, the server has a Swagger-like demo interface at https://bionano-access.scilifelab.se/Bnx/
7
- which is useful for details, and for sniffing actual message content structure, required cookie variable names etc.
4
+ The server API we connect to is described in the following document:
5
+ https://bionano.com/wp-content/uploads/2023/01/30462-Bionano-Access-API-Guide-1.pdf
6
+ For further development, the server has a Swagger-like demo interface at https://bionano-access.scilifelab.se/Bnx/
7
+ which is useful for details, and for sniffing actual message content structure, required cookie variable names etc.
8
8
  """
9
9
 
10
10
  import json
@@ -1,5 +1,5 @@
1
1
  import logging
2
- from typing import Dict
2
+ from typing import Dict, List
3
3
 
4
4
  import requests
5
5
  from flask import current_app
@@ -8,6 +8,9 @@ REF_CHROM = "14"
8
8
  MT_CHROM = "MT"
9
9
  LOG = logging.getLogger(__name__)
10
10
 
11
+ CHANJO_BUILD_37 = "GRCh37"
12
+ CHANJO_BUILD_38 = "GRCh38"
13
+
11
14
 
12
15
  class Chanjo2Client:
13
16
  """Runs requests to chanjo2 and returns results in the expected format."""
@@ -44,3 +47,39 @@ class Chanjo2Client:
44
47
  coverage_stats[ind["individual_id"]] = coverage_info
45
48
 
46
49
  return coverage_stats
50
+
51
+ def get_gene_complete_coverage(
52
+ self, hgnc_id: int, threshold: int = 15, individuals: dict = {}, build: str = "38"
53
+ ) -> bool:
54
+ """
55
+ Return complete coverage for hgnc_id at a coverage threshold.
56
+ """
57
+ chanjo_build = CHANJO_BUILD_37 if "37" in build else CHANJO_BUILD_38
58
+ chanjo2_gene_cov_url: str = "/".join(
59
+ [current_app.config.get("CHANJO2_URL"), "coverage/d4/genes/summary"]
60
+ )
61
+
62
+ gene_cov_query = {
63
+ "build": chanjo_build,
64
+ "coverage_threshold": threshold,
65
+ "hgnc_gene_ids": [hgnc_id],
66
+ "interval_type": "genes",
67
+ "samples": [],
68
+ }
69
+ for ind in individuals:
70
+ if not ind.get("d4_file"):
71
+ continue
72
+
73
+ gene_cov_query["samples"].append(
74
+ {"coverage_file_path": ind["d4_file"], "name": ind["individual_id"]}
75
+ )
76
+
77
+ resp = requests.post(chanjo2_gene_cov_url, json=gene_cov_query)
78
+ gene_cov = resp.json()
79
+
80
+ full_coverage = bool(gene_cov)
81
+ for sample in gene_cov.keys():
82
+ if gene_cov[sample]["coverage_completeness_percent"] < 100:
83
+ full_coverage = False
84
+
85
+ return full_coverage
@@ -1,5 +1,5 @@
1
1
  """
2
- Generate coverage reports using chanjo and chanjo-report. Documentation under -> `docs/admin-guide/chanjo_coverage_integration.md`
2
+ Generate coverage reports using chanjo and chanjo-report. Documentation under -> `docs/admin-guide/chanjo_coverage_integration.md`
3
3
  """
4
4
 
5
5
  import json
@@ -1,5 +1,7 @@
1
1
  import json
2
2
  import logging
3
+ from io import StringIO
4
+ from typing import Optional, Tuple
3
5
 
4
6
  import requests
5
7
  from flask import flash
@@ -17,6 +19,7 @@ class ClinVarApi:
17
19
 
18
20
  def init_app(self, app):
19
21
  self.convert_service = "/".join([PRECLINVAR_URL, "csv_2_json"])
22
+ self.delete_service = "/".join([PRECLINVAR_URL, "delete"])
20
23
  self.submit_service_url = app.config.get("CLINVAR_API_URL") or CLINVAR_API_URL_DEFAULT
21
24
 
22
25
  def set_header(self, api_key) -> dict:
@@ -77,10 +80,61 @@ class ClinVarApi:
77
80
  except Exception as ex:
78
81
  return self.submit_service_url, None, ex
79
82
 
80
- def show_submission_status(self, submission_id: str, api_key=None):
83
+ def json_submission_status(self, submission_id: str, api_key=None) -> dict:
81
84
  """Retrieve the status of a ClinVar submission using the https://submit.ncbi.nlm.nih.gov/api/v1/submissions/SUBnnnnnn/actions/ endpoint."""
82
85
 
83
86
  header: dict = self.set_header(api_key)
84
87
  actions_url = f"{self.submit_service_url}{submission_id}/actions/"
85
88
  actions_resp: requests.models.Response = requests.get(actions_url, headers=header)
86
- flash(f"Response from ClinVar: {actions_resp.json()}", "primary")
89
+ return actions_resp.json()
90
+
91
+ def get_clinvar_scv_accession(self, url: str) -> Optional[str]:
92
+ """Downloads a submission summary from the given URL into a temporary file and parses this file to retrieve a SCV accession, if available."""
93
+ # Send a GET request to download the file
94
+ response = requests.get(url)
95
+ response.raise_for_status() # Raise an error if the request failed
96
+
97
+ # Use an in-memory file-like object to hold the JSON content
98
+ with StringIO(response.text) as memory_file:
99
+
100
+ json_data = json.load(memory_file)
101
+
102
+ submission_data: dict = json_data["submissions"][0]
103
+ processing_status: str = submission_data["processingStatus"]
104
+ if processing_status != "Success":
105
+ flash(
106
+ f"Could not delete provided submission because its processing status is '{processing_status}'.",
107
+ "warning",
108
+ )
109
+ return
110
+ return submission_data["identifiers"]["clinvarAccession"]
111
+
112
+ def delete_clinvar_submission(self, submission_id: str, api_key=None) -> Tuple[int, dict]:
113
+ """Remove a successfully processed submission from ClinVar."""
114
+
115
+ try:
116
+ submission_status_doc: dict = self.json_submission_status(
117
+ submission_id=submission_id, api_key=api_key
118
+ )
119
+
120
+ subm_response: dict = submission_status_doc["actions"][0]["responses"][0]
121
+ submission_status = subm_response["status"]
122
+
123
+ if submission_status != "processed":
124
+ return (
125
+ 500,
126
+ f"Clinvar submission status should be 'processed' and in order to attempt data deletion. Submission status is '{submission_status}'.",
127
+ )
128
+
129
+ # retrieve ClinVar SCV accession (SCVxxxxxxxx) from file url returned by subm_response
130
+ subm_summary_url: str = subm_response["files"][0]["url"]
131
+ scv_accession: Optional(str) = self.get_clinvar_scv_accession(url=subm_summary_url)
132
+
133
+ # Remove ClinVar submission using preClinVar's 'delete' endpoint
134
+ resp = requests.post(
135
+ self.delete_service, data={"api_key": api_key, "clinvar_accession": scv_accession}
136
+ )
137
+ return resp.status_code, resp.json()
138
+
139
+ except Exception as ex:
140
+ return 500, str(ex)