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
@@ -95,7 +95,11 @@ class InstituteForm(FlaskForm):
95
95
 
96
96
  alamut_institution = StringField("Alamut Institution ID", validators=[validators.Optional()])
97
97
 
98
- check_show_all_vars = BooleanField("Preselect 'Show also variants only present in unaffected'")
98
+ check_show_all_vars = BooleanField("Preselect 'Include variants only present in unaffected'")
99
+
100
+ soft_filters = NonValidatingSelectMultipleField(
101
+ "Default soft filters", validators=[validators.Optional()]
102
+ )
99
103
 
100
104
  clinvar_key = StringField("API key", widget=PasswordInput(hide_value=False))
101
105
 
@@ -104,6 +104,13 @@
104
104
  placeholder: "Add Sanger email",
105
105
  });
106
106
 
107
+ $('#soft_filters').select2({
108
+ tags: true,
109
+ theme: 'bootstrap-5',
110
+ tokenSeparators: [','],
111
+ placeholder: "germline_risk",
112
+ });
113
+
107
114
  $('#clinvar_tags').select2({
108
115
  tags: true,
109
116
  theme: 'bootstrap-5',
@@ -285,7 +285,7 @@
285
285
  </div>
286
286
  <!-- End of cohorts settings -->
287
287
 
288
- <!-- Variants and panels searching -->
288
+ <!-- Variants and panels searching -->
289
289
  <div class="row mt-5 d-flex align-items-center">
290
290
  <fieldset>
291
291
  <legend>Variants and gene panels searching</legend>
@@ -349,6 +349,25 @@
349
349
  </div>
350
350
  <!-- End of loqusdb settings -->
351
351
 
352
+ <!-- Custom soft filters for variants -->
353
+ <div class="row mt-5 d-flex align-items-center">
354
+ <fieldset>
355
+ <legend>Variants custom soft filters</legend>
356
+ <div class="row">
357
+ <div class="col-sm-6 col-lg-4">
358
+ {{form.soft_filters.label(class="control-label", data_bs_toggle="tooltip", data_bs_placement="top", title="Values to filter variant documents with by default. For example germline_risk or in_normal.")}}
359
+ <select class="select2" id="soft_filters" name="soft_filters" multiple="true" style="width:100%;">
360
+ {% if institute.soft_filters %}
361
+ {% for filter in institute.soft_filters %}
362
+ <option value="{{filter}}" selected>{{filter}}</option>
363
+ {% endfor %}
364
+ {% endif %}
365
+ </select>
366
+ </div>
367
+ </div>
368
+ </div>
369
+ <!-- End of custom soft filters for variants -->
370
+
352
371
  <!-- Alamut settings -->
353
372
  <div class="row mt-5 d-flex align-items-center">
354
373
  <fieldset><legend>Alamut Plus<a class="ms-2 text-decoration-none" href="https://extranet.interactive-biosoftware.com/alamut-visual-plus_API.html" target="_blank" rel="noopener">*</a></legend>
@@ -59,7 +59,7 @@
59
59
  <tr>
60
60
  <th style="width:14%" title="HGNC symbols">Gene</th>
61
61
  <th style="width:7%" title="Sub-category">Type</th>
62
- <th style="width:7%" title="Value - delta Psi or l2fc ">Value</th>
62
+ <th style="width:7%" title="Value - delta Psi or l2fc">Value</th>
63
63
  <th title="Functional annotation">Func. annotation</th>
64
64
  <th style="width:7%" title="P-value">P-value</th>
65
65
  <th style="width:10%" title="Individual">Ind</th>
@@ -246,6 +246,13 @@
246
246
  <div class="col-6">
247
247
  {{ stash_filter_buttons(form, institute, case) }}
248
248
  </div>
249
+
250
+ <div class="col-2 offset-2">
251
+ <div class="btn-group">
252
+ {{ form.sort_by(class="form-select btn btn-primary", style="width: auto;") }}
253
+ {{ form.sort_order(class="form-select btn btn-primary", style="width: auto;") }}
254
+ </div>
255
+ </div>
249
256
  </div>
250
257
  {% endmacro %}
251
258
 
@@ -266,7 +273,7 @@
266
273
  <div class="col-4 d-flex justify-content-center">
267
274
  Filter returns {{result_size}} / {{ total_variants }} variants.
268
275
  </div>
269
- <div class="col-4 d-flex flex-column justify-content-end">
276
+ <div class="col-4 d-flex justify-content-end">
270
277
  <div class="d-flex justify-content-end">Showing {%if more_variants %}page {{page}}{%else%}last page{% endif %} with {{nvars}} variants.</div>
271
278
  </div>
272
279
  </div>
@@ -11,6 +11,9 @@ from scout.server.blueprints.variants.controllers import (
11
11
  get_variants_page,
12
12
  populate_chrom_choices,
13
13
  populate_filters_form,
14
+ populate_persistent_filters_choices,
15
+ set_hpo_clinical_filter,
16
+ update_form_hgnc_symbols,
14
17
  )
15
18
  from scout.server.blueprints.variants.forms import OutlierFiltersForm
16
19
  from scout.server.extensions import store
@@ -43,8 +46,7 @@ def outliers(institute_id, case_name):
43
46
  variant_type = "clinical"
44
47
  variants_stats = store.case_variants_count(case_obj["_id"], institute_id, variant_type, False)
45
48
 
46
- if request.form.get("hpo_clinical_filter"):
47
- case_obj["hpo_clinical_filter"] = True
49
+ set_hpo_clinical_filter(case_obj, request.form)
48
50
 
49
51
  # update status of case if visited for the first time
50
52
  activate_case(store, institute_obj, case_obj, current_user)
@@ -63,12 +65,6 @@ def outliers(institute_id, case_name):
63
65
  store, institute_obj, case_obj, user_obj, category, request.form
64
66
  )
65
67
 
66
- # populate filters dropdown
67
- available_filters = list(store.filters(institute_obj["_id"], category))
68
- form.filters.choices = [
69
- (filter.get("_id"), filter.get("display_name")) for filter in available_filters
70
- ]
71
-
72
68
  # populate available panel choices
73
69
  form.gene_panels.choices = gene_panel_choices(store, institute_obj, case_obj)
74
70
 
@@ -78,7 +74,7 @@ def outliers(institute_id, case_name):
78
74
  genome_build = "38" if "38" in str(case_obj.get("genome_build", "37")) else "37"
79
75
  cytobands = store.cytoband_by_chrom(genome_build)
80
76
 
81
- # controllers.update_form_hgnc_symbols(store, case_obj, form)
77
+ update_form_hgnc_symbols(store, case_obj, form)
82
78
  variants_query = store.omics_variants(
83
79
  case_obj["_id"], query=form.data, category=category, build=genome_build
84
80
  )
@@ -96,7 +92,9 @@ def outliers(institute_id, case_name):
96
92
  case=case_obj,
97
93
  cytobands=cytobands,
98
94
  expand_search=get_expand_search(request.form),
99
- filters=available_filters,
95
+ filters=populate_persistent_filters_choices(
96
+ institute_id=institute_id, category=category, form=form
97
+ ),
100
98
  form=form,
101
99
  inherit_palette=INHERITANCE_PALETTE,
102
100
  institute=institute_obj,
@@ -32,10 +32,12 @@ from scout.server.blueprints.variant.utils import (
32
32
  update_variant_case_panels,
33
33
  )
34
34
  from scout.server.blueprints.variants.utils import update_case_panels
35
- from scout.server.extensions import LoqusDB, config_igv_tracks, gens
35
+ from scout.server.extensions import LoqusDB, chanjo2, config_igv_tracks, gens
36
36
  from scout.server.links import disease_link, get_variant_links
37
37
  from scout.server.utils import (
38
38
  case_has_alignments,
39
+ case_has_chanjo2_coverage,
40
+ case_has_chanjo_coverage,
39
41
  case_has_mt_alignments,
40
42
  case_has_rna_tracks,
41
43
  user_institutes,
@@ -257,6 +259,8 @@ def variant(
257
259
  # Provide basic info on alignment files availability for this case
258
260
  case_has_alignments(case_obj)
259
261
  case_has_mt_alignments(case_obj)
262
+ case_has_chanjo_coverage(case_obj)
263
+ case_has_chanjo2_coverage(case_obj)
260
264
 
261
265
  # Collect all the events for the variant
262
266
  events = list(store.events(institute_obj, case=case_obj, variant_id=variant_id))
@@ -402,6 +406,9 @@ def variant(
402
406
  "inherit_palette": INHERITANCE_PALETTE,
403
407
  "igv_tracks": get_igv_tracks("38" if variant_obj["is_mitochondrial"] else genome_build),
404
408
  "has_rna_tracks": case_has_rna_tracks(case_obj),
409
+ "gene_has_full_coverage": get_gene_has_full_coverage(
410
+ institute_obj, case_obj, variant_obj, genome_build
411
+ ),
405
412
  "gens_info": gens.connection_settings(genome_build),
406
413
  "evaluations": evaluations,
407
414
  "ccv_evaluations": ccv_evaluations,
@@ -409,6 +416,28 @@ def variant(
409
416
  }
410
417
 
411
418
 
419
+ def get_gene_has_full_coverage(
420
+ institute_obj, case_obj, variant_obj, genome_build
421
+ ) -> Dict[int, bool]:
422
+ """
423
+ Query chanjo2, if configured and d4 files are available for this case,
424
+ for coverage completeness on the genes touching this variant.
425
+ """
426
+ if not case_obj.get("chanjo2_coverage"):
427
+ return {}
428
+
429
+ gene_has_full_coverage: dict = {
430
+ hgnc_id: chanjo2.get_gene_complete_coverage(
431
+ hgnc_id=hgnc_id,
432
+ threshold=institute_obj.get("coverage_cutoff") or 15,
433
+ individuals=case_obj.get("individuals"),
434
+ build=genome_build,
435
+ )
436
+ for hgnc_id in [gene.get("hgnc_id") for gene in variant_obj.get("genes")]
437
+ }
438
+ return gene_has_full_coverage
439
+
440
+
412
441
  def variant_rank_scores(store: MongoAdapter, case_obj: dict, variant_obj: dict) -> list:
413
442
  """Retrive rank score values and ranges for the variant
414
443
 
@@ -223,9 +223,25 @@
223
223
  {% if tmp_gene_info %}
224
224
  {% set primary_transcript = tmp_gene_info[1] %}
225
225
  {% set nr_genes = variant.genes | length %}
226
- <a target="_blank" href="{{ url_for('genes.gene', hgnc_id=primary_gene.hgnc_id) }}" data-bs-toggle="tooltip" title="Preferred gene for variant {% if nr_genes > 1 %} out of {{ nr_genes }} {% endif %}- shows conceptually highest impact change. See Severity card and Transcripts table below for more details."><h5 class="card-title">
227
- {{ primary_gene.hgnc_symbol }}; {{ primary_gene.description }} {% if nr_genes > 1 %} <span class="badge bg-info">+ {{ nr_genes-1 }} genes</span>{% endif %}
228
- </h5></a>
226
+ <table class="table table-bordered table-fixed table-sm">
227
+ <thead class="thead table-light border-top">
228
+ <tr class="active">
229
+ <th>Gene</th>
230
+ <th>Description</th>
231
+ <th data-bs-toggle="tooltip" title="Gene tolerance to a single copy of a truncating mutation (pLI) | Loss-of-function observed/expected upper bound fraction (LOEUF)">pLI score|LOEUF</th>
232
+ </tr>
233
+ </thead>
234
+ <tbody>
235
+ <tr>
236
+ <td>
237
+ <a target="_blank" href="{{ url_for('genes.gene', hgnc_id=primary_gene.hgnc_id) }}" data-bs-toggle="tooltip" title="Preferred gene for variant {% if nr_genes > 1 %} out of {{ nr_genes }} {% endif %}- shows conceptually highest impact change. See Severity card and Transcripts table below for more details.">{{ primary_gene.hgnc_symbol }}</a>
238
+ {% if nr_genes > 1 %} <span class="badge bg-info">+ {{ nr_genes-1 }} genes</span>{% endif %}
239
+ </td>
240
+ <td>{{primary_gene.description}}</td>
241
+ <td>{{primary_gene.pli_score|round(2) if primary_gene.pli_score else "n.a."}} | {{primary_gene.loeuf|round(2) if primary_gene.loeuf else "n.a."}}</td>
242
+ </tr>
243
+ </tbody>
244
+ </table>
229
245
  {% endif %}
230
246
  <ul class="list-group list-group-flush" style="margin-bottom: 1rem">
231
247
  {% if primary_transcript %}
@@ -235,10 +251,10 @@
235
251
  <span class="text-muted">exon </span><strong>{{ primary_gene.exon }}</strong>
236
252
  {% endif %}
237
253
  {% if primary_transcript and primary_transcript.coding_sequence_name %}
238
- {{ primary_transcript.coding_sequence_name | url_decode }}
254
+ {{ primary_transcript.coding_sequence_name | url_decode | truncate(50, True) }}
239
255
  {% endif %}
240
256
  {% if primary_transcript and primary_transcript.protein_sequence_name %}
241
- <strong>{{ primary_transcript.protein_sequence_name | url_decode }}</strong>
257
+ <strong>{{ primary_transcript.protein_sequence_name | url_decode | truncate(50, True) }}</strong>
242
258
  {% endif %}
243
259
  {% if primary_gene and primary_gene.region_annotation %}
244
260
  <div>(<span class="font-weight-bold">{{ primary_gene.functional_annotation|truncate(20, True) }}</span> in <span class="font-weight-bold">{{ primary_gene.region_annotation }} region</span>)</div>
@@ -1,3 +1,4 @@
1
+ {% from "cases/chanjo2_form.html" import chanjo2_report_form %}
1
2
  {% from "variant/buttons.html" import variant_tag_button, variant_tier_button, dismiss_variant_button, mosaic_variant_button %}
2
3
  {% from "variants/utils.html" import compounds_table %}
3
4
  {% from "variant/variant_details.html" import severity_list %}
@@ -118,15 +119,7 @@
118
119
  {% endif %}
119
120
  </div>
120
121
  {% endfor %}
121
- {% if config.chanjo_report %}
122
- <div class="d-flex flex-wrap ms-1">
123
- {% for gene in variant.genes %}
124
- <a class="btn btn-sm btn-secondary text-white" rel="noopener noreferrer" target="_blank" href="{{ url_for('report.gene', gene_id=gene.hgnc_id, sample_id=variant.samples|map(attribute='sample_id')|list) }}">
125
- Gene coverage: {{ gene.common.hgnc_symbol if gene.common else gene.hgnc_id }}
126
- </a>
127
- {% endfor %}
128
- </div>
129
- {% endif %}
122
+ {{ gene_coverage(institute, variant, case, config) }}
130
123
  </li>
131
124
  <li class="list-group-item">
132
125
  <div>
@@ -136,6 +129,30 @@
136
129
  </ul>
137
130
  {% endmacro %}
138
131
 
132
+ {% macro gene_coverage(institute, variant, case, config) %}
133
+ {% if case.chanjo_coverage and config.chanjo_report %}
134
+ <div class="d-flex flex-wrap ms-1">
135
+ {% for gene in variant.genes %}
136
+ <a class="btn btn-sm btn-secondary text-white" rel="noopener noreferrer" target="_blank" href="{{ url_for('report.gene', gene_id=gene.hgnc_id, sample_id=variant.samples|map(attribute='sample_id')|list) }}" data-bs-toggle="tooltip" title="Chanjo coverage report">
137
+ Gene coverage: {{ gene.common.hgnc_symbol if gene.common else gene.hgnc_id }}
138
+ </a>
139
+ {% endfor %}
140
+ </div>
141
+ {% endif %}
142
+ {% if case.chanjo2_coverage %}
143
+ <div class="d-flex flex-wrap ms-1" data-bs-toggle="tooltip" title="Chanjo2 coverage reports">
144
+ {% for gene in variant.genes %}
145
+ {{ chanjo2_report_form(institute, case, gene.hgnc_symbol, 'overview', gene.hgnc_id, "Gene coverage " + (gene.common.hgnc_symbol if gene.common else gene.hgnc_id), "btn btn-sm btn-secondary text-white") }} <!--chanjo2 genes overview -->
146
+ {% if gene.common.hgnc_id in gene_has_full_coverage and gene.common.hgnc_id[gene.common.hgnc_id] %}
147
+ <span class="bg-success fa-solid fa-circle-check" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Chanjo2 coverage is at 100% completeness."></span>
148
+ {% else %}
149
+ <span class="bg-warning fa-solid fa-triangle-exclamation" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Note that Chanjo2 coverage is below 100% completeness."></span>
150
+ {% endif %}
151
+ {% endfor %}
152
+ </div>
153
+ {% endif %}
154
+ {% endmacro %}
155
+
139
156
  {% macro acmg_form(institute, case, variant, ACMG_OPTIONS, selected=None) %}
140
157
  <form action="{{ url_for('variant.variant_update', institute_id=institute._id, case_name=case.display_name, variant_id=variant._id) }}" method="POST">
141
158
  <div class="d-flex justify-content-between">
@@ -339,6 +339,7 @@
339
339
  <thead class="thead table-light border-top">
340
340
  <tr class="active">
341
341
  <th>Gene</th>
342
+ <th data-bs-toggle="tooltip" title="Gene tolerance to a single copy of a truncating mutation (pLI) | Loss-of-function observed/expected upper bound fraction (LOEUF)">pLI score|LOEUF</th>
342
343
  <th>Region</th>
343
344
  <th>Consequence</th>
344
345
  {% if case.genome_build == "38" %}
@@ -349,11 +350,12 @@
349
350
  <tbody>
350
351
  {% for gene in variant.genes %}
351
352
  <tr>
352
- <th>
353
+ <td>
353
354
  <a href="{{ url_for('genes.gene', hgnc_id=gene.hgnc_id) }}">
354
355
  {{ gene.common.hgnc_symbol if gene.common else gene.hgnc_id }}
355
356
  </a>
356
- </th>
357
+ </td>
358
+ <td>{{gene.pli_score|round(2) if gene.pli_score else "n.a."}} | {{gene.loeuf|round(2) if gene.loeuf else "n.a."}}</td>
357
359
  <td>{{ gene.region_annotation }}</td>
358
360
  <td>{{ gene.functional_annotation|truncate(20, True) }}</td>
359
361
  {% if case.genome_build == "38" %} <!-- Display eventual functional annotations associated to MANE transcripts -->
@@ -375,7 +375,7 @@
375
375
  </li>
376
376
  <li class="list-group-item">
377
377
  SPIDEX
378
- <span class="float-end">{{ variant.spidex_human }}</span>
378
+ <span class="float-end">{{ variant.spidex|spidex_human if variant.spidex else none|spidex_human }}</span>
379
379
  </li>
380
380
  <li class="list-group-item">
381
381
  <a href="{{ variant.spliceai_link }}" target="_blank" rel="noopener">SpliceAI</a> <a href="https://github.com/Illumina/SpliceAI" target="_blank" rel="noopener">DS max</a>
@@ -282,6 +282,8 @@ def add_gene_info(
282
282
  )
283
283
 
284
284
  all_models.update(set(variant_gene["manual_inheritance"]))
285
+ variant_gene["pli_score"] = hgnc_gene.get("pli_score")
286
+ variant_gene["loeuf"] = hgnc_gene.get("constraint_lof_oe_ci_upper")
285
287
 
286
288
  update_inheritance_model(variant_gene, all_models, disease_terms)
287
289
 
@@ -27,12 +27,19 @@ from scout.server.blueprints.variant.controllers import (
27
27
  check_reset_variant_classification,
28
28
  )
29
29
  from scout.server.blueprints.variant.controllers import evaluation as evaluation_controller
30
- from scout.server.blueprints.variant.controllers import observations, str_variant_reviewer
30
+ from scout.server.blueprints.variant.controllers import (
31
+ observations,
32
+ str_variant_reviewer,
33
+ )
31
34
  from scout.server.blueprints.variant.controllers import variant as variant_controller
32
35
  from scout.server.blueprints.variant.controllers import variant_acmg as acmg_controller
33
- from scout.server.blueprints.variant.controllers import variant_acmg_post
36
+ from scout.server.blueprints.variant.controllers import (
37
+ variant_acmg_post,
38
+ )
34
39
  from scout.server.blueprints.variant.controllers import variant_ccv as ccv_controller
35
- from scout.server.blueprints.variant.controllers import variant_ccv_post
40
+ from scout.server.blueprints.variant.controllers import (
41
+ variant_ccv_post,
42
+ )
36
43
  from scout.server.blueprints.variant.verification_controllers import (
37
44
  MissingVerificationRecipientError,
38
45
  variant_verification,
@@ -40,6 +40,7 @@ from scout.server.blueprints.variant.utils import (
40
40
  update_variant_case_panels,
41
41
  )
42
42
  from scout.server.blueprints.variants.forms import BetterDecimalField
43
+ from scout.server.extensions import store
43
44
  from scout.server.links import add_gene_links, cosmic_links, str_source_link
44
45
  from scout.server.utils import (
45
46
  case_has_alignments,
@@ -83,6 +84,20 @@ def populate_force_show_unaffected_vars(institute_obj, form):
83
84
  form.show_unaffected.data = True
84
85
 
85
86
 
87
+ def populate_persistent_filters_choices(
88
+ institute_id: str, category: str, form: ImmutableMultiDict
89
+ ) -> List[dict]:
90
+ """Populate the options present in the form.filters on variants page, directly setting on the form.
91
+ Also return a convenient list of filters for use in filter update macros.
92
+ """
93
+ available_filters = list(store.filters(institute_id, category))
94
+ form.filters.choices = [
95
+ (filter.get("_id"), filter.get("display_name"))
96
+ for filter in sorted(available_filters, key=lambda f: f.get("display_name", "").lower())
97
+ ]
98
+ return available_filters
99
+
100
+
86
101
  def populate_chrom_choices(form, case_obj):
87
102
  """Populate the option of the chromosome select according to the case genome build"""
88
103
  # Populate chromosome choices
@@ -90,6 +105,12 @@ def populate_chrom_choices(form, case_obj):
90
105
  form.chrom.choices = [(chrom, chrom) for chrom in chromosomes]
91
106
 
92
107
 
108
+ def populate_institute_soft_filters(form, institute_obj):
109
+ """Populate the hidden field 'institute_soft_filters' with a string containing all institute's soft filters."""
110
+ if institute_obj.get("soft_filters"):
111
+ form.institute_soft_filters.data = ",".join(institute_obj["soft_filters"])
112
+
113
+
93
114
  def variants(
94
115
  store,
95
116
  institute_obj,
@@ -711,7 +732,7 @@ def _compound_follow_filter_clnsig(compound, compound_var_obj, query_form):
711
732
 
712
733
  There are some filter options that are rather unique, like the ClinVar one.
713
734
 
714
- If clinsig_confident_always_returned is checked, variants are currently never dismissed on ClinSig alone.
735
+ If prioritise_clinvar is checked, variants are currently never dismissed on ClinSig alone.
715
736
 
716
737
  Args:
717
738
  compound(dict)
@@ -722,8 +743,7 @@ def _compound_follow_filter_clnsig(compound, compound_var_obj, query_form):
722
743
  query_rank = []
723
744
  query_str_rank = []
724
745
 
725
- clinsig_always_returned = query_form.get("clinsig_confident_always_returned")
726
- if clinsig_always_returned:
746
+ if query_form.get("prioritise_clinvar"):
727
747
  return False
728
748
 
729
749
  clinsig = query_form.get("clinsig")
@@ -2088,3 +2108,9 @@ def get_show_dismiss_block():
2088
2108
  session["show_dismiss_block"] = show_dismiss_block
2089
2109
 
2090
2110
  return show_dismiss_block
2111
+
2112
+
2113
+ def set_hpo_clinical_filter(case_obj: dict, request_form: dict):
2114
+ """Set HPO clinical filter on case if requested through the form."""
2115
+ if request_form.get("hpo_clinical_filter"):
2116
+ case_obj["hpo_clinical_filter"] = True
@@ -113,6 +113,8 @@ class VariantFiltersForm(FlaskForm):
113
113
  compound_follow_filter = BooleanField("Compounds follow filter")
114
114
  cadd_inclusive = BooleanField("CADD inclusive")
115
115
  clinsig = NonValidatingSelectMultipleField("ClinVar CLINSIG", choices=CLINSIG_OPTIONS)
116
+ clinsig_exclude = BooleanField("Exclude")
117
+ prioritise_clinvar = BooleanField("Prioritise ClinVar")
116
118
 
117
119
  gnomad_frequency = BetterDecimalField("gnomadAF", validators=[validators.Optional()])
118
120
  local_obs_old = IntegerField("Local obs. (archive)", validators=[validators.Optional()])
@@ -151,11 +153,25 @@ class VariantFiltersForm(FlaskForm):
151
153
  cytoband_start = NonValidatingSelectField("Cytoband start", choices=[])
152
154
  cytoband_end = NonValidatingSelectField("Cytoband end", choices=[])
153
155
 
156
+ size_selector = NonValidatingSelectField(
157
+ "Variant size in bp", choices=[("$gte", ">="), ("$lt", "<")]
158
+ )
159
+ size = IntegerField(
160
+ "",
161
+ [
162
+ validators.Optional(),
163
+ validators.NumberRange(min=0, message="Number of bases must be 1 or greater."),
164
+ ],
165
+ widget=NumberInput(min=1),
166
+ )
167
+
154
168
  hide_dismissed = BooleanField("Hide dismissed", default=False)
155
169
  filter_variants = SubmitField(label="Filter variants")
156
170
  export = SubmitField(label="Filter and export")
157
171
 
158
- show_unaffected = BooleanField("Show also variants present only in unaffected", default=False)
172
+ show_unaffected = BooleanField("Include variants present only in unaffected", default=True)
173
+ show_soft_filtered = BooleanField(f"Include soft-filtered variants", default=False)
174
+ institute_soft_filters = HiddenField()
159
175
 
160
176
 
161
177
  class FiltersForm(VariantFiltersForm):
@@ -163,11 +179,11 @@ class FiltersForm(VariantFiltersForm):
163
179
 
164
180
  symbol_file = FileField("Symbol File")
165
181
 
166
- clinsig_confident_always_returned = BooleanField("CLINSIG Confident")
182
+ clinvar_trusted_revstat = BooleanField("CLINSIG Confident")
167
183
  spidex_human = SelectMultipleField("SPIDEX", choices=SPIDEX_CHOICES)
168
184
 
169
185
  clinical_filter = SubmitField(label="Clinical filter")
170
- clinvar_tag = BooleanField("ClinVar hits")
186
+ clinvar_tag = BooleanField("ClinVar hits only")
171
187
 
172
188
  # polymorphic constant base for clinical filter
173
189
  clinical_filter_base = CLINICAL_FILTER_BASE
@@ -180,7 +196,7 @@ class CancerFiltersForm(VariantFiltersForm):
180
196
  alt_count = IntegerField("Min alt count", validators=[validators.Optional()])
181
197
  control_frequency = BetterDecimalField("Normal alt AF <", validators=[validators.Optional()])
182
198
  tumor_frequency = BetterDecimalField("Tumor alt AF >", validators=[validators.Optional()])
183
- clinvar_tag = BooleanField("ClinVar hits")
199
+ clinvar_tag = BooleanField("ClinVar hits only")
184
200
  cosmic_tag = BooleanField("Cosmic hits")
185
201
  mvl_tag = BooleanField("Managed Variants hits")
186
202
  local_obs_cancer_somatic_old = IntegerField(
@@ -189,7 +205,6 @@ class CancerFiltersForm(VariantFiltersForm):
189
205
  local_obs_cancer_germline_old = IntegerField(
190
206
  "Local germline obs. (archive)", validators=[validators.Optional()]
191
207
  )
192
-
193
208
  # polymorphic constant base for clinical filter
194
209
  clinical_filter_base = CLINICAL_FILTER_BASE_CANCER
195
210
 
@@ -212,8 +227,6 @@ class StrFiltersForm(VariantFiltersForm):
212
227
  class SvFiltersForm(VariantFiltersForm):
213
228
  """Extends FiltersForm for structural variants"""
214
229
 
215
- size = StringField("Length")
216
- size_shorter = BooleanField("Length shorter than")
217
230
  svtype = SelectMultipleField("SVType", choices=SV_TYPE_CHOICES)
218
231
  decipher = BooleanField("Decipher")
219
232
  clingen_ngi = IntegerField("ClinGen NGI obs")
@@ -242,8 +255,6 @@ class CancerSvFiltersForm(SvFiltersForm):
242
255
  class FusionFiltersForm(VariantFiltersForm):
243
256
  """Extends FiltersForm for fusion variants"""
244
257
 
245
- size = StringField("Length")
246
- size_shorter = BooleanField("Length shorter than")
247
258
  decipher = BooleanField("Decipher")
248
259
  clinical_filter = SubmitField(label="Clinical filter")
249
260
  fusion_score = BetterDecimalField("Fusion score >=", validators=[validators.Optional()])
@@ -302,7 +313,23 @@ class OutlierFiltersForm(FlaskForm):
302
313
 
303
314
  clinical_filter_base = CLINICAL_FILTER_BASE_OUTLIER
304
315
 
305
- show_unaffected = BooleanField("Show also variants present only in unaffected", default=False)
316
+ show_unaffected = BooleanField("Include variants present only in unaffected", default=False)
317
+
318
+ sort_by = NonValidatingSelectField(
319
+ choices=[
320
+ ("", "Sort by"),
321
+ ("p_value", "Sort by P-value"),
322
+ ("delta_psi", "Sort by Δψ"),
323
+ ("psi_value", "Sort by ψ value"),
324
+ ("zscore", "Sort by zscore"),
325
+ ("l2fc", "Sort by l2fc"),
326
+ ],
327
+ validators=[validators.Optional()],
328
+ )
329
+ sort_order = NonValidatingSelectField(
330
+ choices=[("", "Sort order"), ("asc", "asc"), ("desc", "desc")],
331
+ validators=[validators.Optional()],
332
+ )
306
333
 
307
334
 
308
335
  FILTERSFORMCLASS = {
@@ -83,17 +83,18 @@
83
83
  </td>
84
84
  <td>{{ gene_cell(variant) }}</td>
85
85
  <td>
86
+
86
87
  <a target="_blank" href="{{ url_for('variant.cancer_variant', institute_id=institute._id, case_name=case.display_name,
87
88
  variant_id=variant._id, cancer='yes') }}">
88
89
  {% if variant.first_rep_gene.hgvs_identifier %}
89
- <div>{{ variant.first_rep_gene.hgvs_identifier }}</div>
90
- <div>{{ (variant.first_rep_gene.hgvsp_identifier or '') |url_decode }}</div>
90
+ <div>{{ variant.first_rep_gene.hgvs_identifier|truncate(30, True) }}</div>
91
+ <div>{{ (variant.first_rep_gene.hgvsp_identifier or '') |url_decode|truncate(30, True) }}</div>
91
92
  {% else %}
92
- <div>{{ variant.reference }}→
93
+ <div>{{ variant.reference|truncate(30, True) }}→
93
94
  {% if variant.alternative | length > 5 %}
94
95
  {{ variant.alternative[0] }}...{{ variant.alternative[-1] }}
95
96
  {% else %}
96
- {{ variant.alternative }}
97
+ {{ variant.alternative|truncate(30, True) }}
97
98
  {% endif %}
98
99
  </div>
99
100
  {% endif %}
@@ -203,16 +203,18 @@
203
203
  {% endfor %}
204
204
 
205
205
  {% set panel_count = matching_panels|length %}
206
- <a
207
- class="badge bg-secondary text-white"
208
- data-bs-toggle="popover"
209
- data-bs-html="true"
210
- data-bs-trigger="hover click"
211
- title="Overlapping gene panels"
212
- data-bs-content="{% for panel in matching_panels %}
213
- {{ panel.panel_name|safe }}<br>
214
- {% else %} No ovelapping gene panels {% endfor %}"
215
- >{{ panel_count }}</a>
206
+ {% if panel_count %}
207
+ <a
208
+ class="badge bg-secondary text-white"
209
+ data-bs-toggle="popover"
210
+ data-bs-html="true"
211
+ data-bs-trigger="hover click"
212
+ title="Overlapping gene panels"
213
+ data-bs-content="{% for panel in matching_panels %}
214
+ {{ panel.panel_name|safe }}<br>
215
+ {% endfor %}"
216
+ >{{ panel_count }}</a>
217
+ {% endif %}
216
218
  {% endmacro %}
217
219
 
218
220
  {% if variant.category in ["cancer", "sv_cancer"] %}