scout-browser 4.82.2__py3-none-any.whl → 4.83__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 (57) hide show
  1. scout/__version__.py +1 -1
  2. scout/adapter/client.py +1 -0
  3. scout/adapter/mongo/base.py +0 -1
  4. scout/adapter/mongo/case.py +15 -37
  5. scout/adapter/mongo/case_events.py +98 -2
  6. scout/adapter/mongo/hgnc.py +39 -22
  7. scout/adapter/mongo/institute.py +3 -9
  8. scout/adapter/mongo/panel.py +2 -1
  9. scout/adapter/mongo/variant.py +3 -2
  10. scout/adapter/mongo/variant_loader.py +92 -79
  11. scout/commands/base.py +1 -0
  12. scout/commands/update/case.py +10 -10
  13. scout/commands/update/individual.py +6 -1
  14. scout/constants/file_types.py +4 -0
  15. scout/load/__init__.py +0 -1
  16. scout/load/all.py +3 -4
  17. scout/load/panel.py +8 -4
  18. scout/load/setup.py +1 -0
  19. scout/models/case/case_loading_models.py +6 -16
  20. scout/parse/case.py +0 -1
  21. scout/parse/disease_terms.py +1 -0
  22. scout/parse/omim.py +1 -0
  23. scout/parse/panel.py +40 -15
  24. scout/resources/__init__.py +3 -0
  25. scout/server/app.py +4 -50
  26. scout/server/blueprints/alignviewers/controllers.py +15 -17
  27. scout/server/blueprints/alignviewers/templates/alignviewers/igv_viewer.html +13 -3
  28. scout/server/blueprints/alignviewers/views.py +10 -15
  29. scout/server/blueprints/cases/controllers.py +70 -73
  30. scout/server/blueprints/cases/templates/cases/case.html +37 -21
  31. scout/server/blueprints/cases/templates/cases/collapsible_actionbar.html +1 -1
  32. scout/server/blueprints/cases/templates/cases/phenotype.html +8 -6
  33. scout/server/blueprints/cases/templates/cases/utils.html +3 -3
  34. scout/server/blueprints/cases/views.py +8 -6
  35. scout/server/blueprints/variant/controllers.py +5 -5
  36. scout/server/blueprints/variant/templates/variant/acmg.html +25 -16
  37. scout/server/blueprints/variant/templates/variant/components.html +11 -6
  38. scout/server/blueprints/variant/views.py +5 -2
  39. scout/server/blueprints/variants/controllers.py +1 -1
  40. scout/server/blueprints/variants/views.py +1 -1
  41. scout/server/config.py +16 -4
  42. scout/server/extensions/__init__.py +4 -2
  43. scout/server/extensions/beacon_extension.py +1 -0
  44. scout/server/extensions/chanjo_extension.py +58 -0
  45. scout/server/extensions/phenopacket_extension.py +1 -0
  46. scout/server/static/bs_styles.css +18 -0
  47. scout/server/utils.py +16 -2
  48. scout/utils/acmg.py +33 -20
  49. scout/utils/track_resources.py +70 -0
  50. {scout_browser-4.82.2.dist-info → scout_browser-4.83.dist-info}/METADATA +1 -1
  51. {scout_browser-4.82.2.dist-info → scout_browser-4.83.dist-info}/RECORD +55 -55
  52. scout/load/case.py +0 -36
  53. scout/utils/cloud_resources.py +0 -61
  54. {scout_browser-4.82.2.dist-info → scout_browser-4.83.dist-info}/LICENSE +0 -0
  55. {scout_browser-4.82.2.dist-info → scout_browser-4.83.dist-info}/WHEEL +0 -0
  56. {scout_browser-4.82.2.dist-info → scout_browser-4.83.dist-info}/entry_points.txt +0 -0
  57. {scout_browser-4.82.2.dist-info → scout_browser-4.83.dist-info}/top_level.txt +0 -0
@@ -48,6 +48,7 @@ from scout.server.utils import (
48
48
  case_has_chanjo2_coverage,
49
49
  case_has_chanjo_coverage,
50
50
  case_has_mt_alignments,
51
+ case_has_mtdna_report,
51
52
  case_has_rna_tracks,
52
53
  institute_and_case,
53
54
  )
@@ -62,6 +63,8 @@ JSON_HEADERS = {
62
63
 
63
64
  COVERAGE_REPORT_TIMEOUT = 20
64
65
 
66
+ PANEL_PROJECTION = {"version": 1, "display_name": 1, "genes": 1}
67
+
65
68
 
66
69
  def phenomizer_diseases(hpo_ids, case_obj, p_value_treshold=1):
67
70
  """Return the list of HGNC symbols that match annotated HPO terms on Phenomizer
@@ -278,22 +281,16 @@ def sma_case(store, institute_obj, case_obj):
278
281
  return data
279
282
 
280
283
 
281
- def case(store, institute_obj, case_obj):
284
+ def case(
285
+ store: MongoAdapter, institute_obj: dict, case_obj: dict, hide_matching: bool = True
286
+ ) -> dict:
282
287
  """Preprocess a single case.
283
288
 
284
289
  Prepare the case to be displayed in the case view.
285
290
 
286
- Args:
287
- store(adapter.MongoAdapter)
288
- institute_obj(models.Institute)
289
- case_obj(models.Case)
290
-
291
- Returns:
292
- data(dict): includes the cases, how many there are and the limit.
293
-
291
+ The return data dict includes the cases, how many there are and the limit.
294
292
  """
295
293
  # Convert individual information to more readable format
296
-
297
294
  _populate_case_individuals(case_obj)
298
295
 
299
296
  case_obj["assignees"] = [
@@ -305,6 +302,7 @@ def case(store, institute_obj, case_obj):
305
302
  case_has_mt_alignments(case_obj)
306
303
  case_has_chanjo_coverage(case_obj)
307
304
  case_has_chanjo2_coverage(case_obj)
305
+ case_has_mtdna_report(case_obj)
308
306
 
309
307
  case_groups = {}
310
308
  case_group_label = {}
@@ -395,31 +393,40 @@ def case(store, institute_obj, case_obj):
395
393
  "case_images", case_obj["custom_images"].get("case", {})
396
394
  )
397
395
 
398
- # Limit secondary findings according to institute settings
399
- limit_genes = store.safe_genes_filter(institute_obj["_id"])
396
+ other_causatives = []
397
+ other_causatives_in_default_panels = []
398
+ default_managed_variants = []
399
+ managed_variants = []
400
400
 
401
- limit_genes_default_panels = _limit_genes_on_default_panels(
402
- case_obj["default_genes"], limit_genes
403
- )
401
+ if hide_matching is False:
402
+ # Limit secondary findings according to institute settings
403
+ limit_genes = store.safe_genes_filter(institute_obj["_id"])
404
404
 
405
- other_causatives, other_causatives_in_default_panels = _matching_causatives(
406
- store, case_obj, limit_genes, limit_genes_default_panels
407
- )
405
+ limit_genes_default_panels = _limit_genes_on_default_panels(
406
+ case_obj["default_genes"], limit_genes
407
+ )
408
408
 
409
- data = {
410
- "institute": institute_obj,
411
- "case": case_obj,
412
- "other_causatives": other_causatives,
413
- "default_other_causatives": other_causatives_in_default_panels,
414
- "managed_variants": [
409
+ other_causatives, other_causatives_in_default_panels = _matching_causatives(
410
+ store, case_obj, limit_genes, limit_genes_default_panels
411
+ )
412
+
413
+ managed_variants = [
415
414
  var for var in store.check_managed(case_obj=case_obj, limit_genes=limit_genes)
416
- ],
417
- "default_managed_variants": [
415
+ ]
416
+ default_managed_variants = [
418
417
  var
419
418
  for var in store.check_managed(
420
419
  case_obj=case_obj, limit_genes=limit_genes_default_panels
421
420
  )
422
- ],
421
+ ]
422
+
423
+ data = {
424
+ "institute": institute_obj,
425
+ "case": case_obj,
426
+ "other_causatives": other_causatives,
427
+ "default_other_causatives": other_causatives_in_default_panels,
428
+ "managed_variants": managed_variants,
429
+ "default_managed_variants": default_managed_variants,
423
430
  "comments": store.events(institute_obj, case=case_obj, comments=True),
424
431
  "hpo_groups": pheno_groups,
425
432
  "case_groups": case_groups,
@@ -441,6 +448,7 @@ def case(store, institute_obj, case_obj):
441
448
  "mme_nodes": matchmaker.connected_nodes,
442
449
  "gens_info": gens.connection_settings(case_obj.get("genome_build")),
443
450
  "display_rerunner": rerunner.connection_settings.get("display", False),
451
+ "hide_matching": hide_matching,
444
452
  }
445
453
 
446
454
  return data
@@ -496,8 +504,12 @@ def _get_default_panel_genes(store: MongoAdapter, case_obj: dict) -> list:
496
504
  continue
497
505
  panel_name = panel_info["panel_name"]
498
506
  panel_version = panel_info.get("version")
499
- panel_obj = store.gene_panel(panel_name, version=panel_version)
500
- latest_panel = store.gene_panel(panel_name)
507
+ panel_obj = store.gene_panel(
508
+ panel_name,
509
+ version=panel_version,
510
+ projection=PANEL_PROJECTION,
511
+ )
512
+ latest_panel = store.gene_panel(panel_name, projection=PANEL_PROJECTION)
501
513
  panel_info["removed"] = False if latest_panel is None else latest_panel.get("hidden", False)
502
514
  if not panel_obj:
503
515
  panel_obj = latest_panel
@@ -555,19 +567,17 @@ def check_outdated_gene_panel(panel_obj, latest_panel):
555
567
  missing_genes, extra_genes
556
568
  """
557
569
  # Create a list of minified gene object for the case panel {hgnc_id, gene_symbol}
558
- case_panel_genes = [
559
- {"hgnc_id": gene["hgnc_id"], "symbol": gene.get("symbol", gene["hgnc_id"])}
560
- for gene in panel_obj["genes"]
561
- ]
570
+ case_panel_genes = set([gene.get("symbol", gene["hgnc_id"]) for gene in panel_obj["genes"]])
562
571
  # And for the latest panel
563
- latest_panel_genes = [
564
- {"hgnc_id": gene["hgnc_id"], "symbol": gene.get("symbol", gene["hgnc_id"])}
565
- for gene in latest_panel["genes"]
566
- ]
572
+ latest_panel_genes = set(
573
+ [gene.get("symbol", gene["hgnc_id"]) for gene in latest_panel["genes"]]
574
+ )
567
575
  # Extract the genes unique to case panel
568
- extra_genes = [gene["symbol"] for gene in case_panel_genes if gene not in latest_panel_genes]
576
+ extra_genes = case_panel_genes.difference(latest_panel_genes)
577
+
569
578
  # Extract the genes unique to latest panel
570
- missing_genes = [gene["symbol"] for gene in latest_panel_genes if gene not in case_panel_genes]
579
+ missing_genes = latest_panel_genes.difference(case_panel_genes)
580
+
571
581
  return extra_genes, missing_genes
572
582
 
573
583
 
@@ -674,7 +684,7 @@ def case_report_content(store: MongoAdapter, institute_obj: dict, case_obj: dict
674
684
  return data
675
685
 
676
686
 
677
- def mt_coverage_stats(individuals, ref_chrom="14"):
687
+ def mt_coverage_stats(individuals, ref_chrom="14") -> dict:
678
688
  """Send a request to chanjo report endpoint to retrieve MT vs autosome coverage stats
679
689
 
680
690
  Args:
@@ -732,10 +742,9 @@ def mt_excel_files(store, case_obj, temp_excel_dir):
732
742
  """
733
743
  today = datetime.datetime.now().strftime(DATE_DAY_FORMATTER)
734
744
  samples = case_obj.get("individuals")
735
- file_header = MT_EXPORT_HEADER
736
745
  coverage_stats = None
737
746
  # if chanjo connection is established, include MT vs AUTOSOME coverage stats
738
- if current_app.config.get("SQLALCHEMY_DATABASE_URI"):
747
+ if current_app.config.get("chanjo_report"):
739
748
  coverage_stats = mt_coverage_stats(samples)
740
749
 
741
750
  query = {"chrom": "MT"}
@@ -765,9 +774,8 @@ def mt_excel_files(store, case_obj, temp_excel_dir):
765
774
  for col, field in enumerate(line): # each field in line becomes a cell
766
775
  Report_Sheet.write(row, col, field)
767
776
 
768
- if bool(
769
- coverage_stats
770
- ): # it's None if app is not connected to Chanjo or {} if samples are not in Chanjo db
777
+ # coverage_stats is None if app is not connected to Chanjo or {} if samples are not in Chanjo db
778
+ if coverage_stats and sample_id in coverage_stats:
771
779
  # Write coverage stats header after introducing 2 empty lines
772
780
  for col, field in enumerate(MT_COV_STATS_HEADER):
773
781
  Report_Sheet.write(row + 3, col, field)
@@ -805,27 +813,6 @@ def update_synopsis(store, institute_obj, case_obj, user_obj, new_synopsis):
805
813
  store.update_synopsis(institute_obj, case_obj, user_obj, link, content=new_synopsis)
806
814
 
807
815
 
808
- def _update_case(store, case_obj, user_obj, institute_obj, verb):
809
- """Update case with new sample data, and create an associated event"""
810
- store.update_case(case_obj, keep_date=True)
811
-
812
- link = url_for(
813
- "cases.case",
814
- institute_id=institute_obj["_id"],
815
- case_name=case_obj["display_name"],
816
- )
817
-
818
- store.create_event(
819
- institute=institute_obj,
820
- case=case_obj,
821
- user=user_obj,
822
- link=link,
823
- category="case",
824
- verb=verb,
825
- subject=case_obj["display_name"],
826
- )
827
-
828
-
829
816
  def update_individuals(store, institute_obj, case_obj, user_obj, ind, age, tissue):
830
817
  """Handle update of individual data (age and/or Tissue type) for a case"""
831
818
 
@@ -841,8 +828,13 @@ def update_individuals(store, institute_obj, case_obj, user_obj, ind, age, tissu
841
828
 
842
829
  case_obj["individuals"] = case_individuals
843
830
 
844
- verb = "update_individual"
845
- _update_case(store, case_obj, user_obj, institute_obj, verb)
831
+ link = url_for(
832
+ "cases.case",
833
+ institute_id=institute_obj["_id"],
834
+ case_name=case_obj["display_name"],
835
+ )
836
+
837
+ store.update_case_individual(case_obj, user_obj, institute_obj, link)
846
838
 
847
839
 
848
840
  def update_cancer_samples(
@@ -866,8 +858,13 @@ def update_cancer_samples(
866
858
 
867
859
  case_obj["individuals"] = case_samples
868
860
 
869
- verb = "update_sample"
870
- _update_case(store, case_obj, user_obj, institute_obj, verb)
861
+ link = url_for(
862
+ "cases.case",
863
+ institute_id=institute_obj["_id"],
864
+ case_name=case_obj["display_name"],
865
+ )
866
+
867
+ store.update_case_sample(case_obj, user_obj, institute_obj, link)
871
868
 
872
869
 
873
870
  def _all_hpo_gene_list_genes(
@@ -1431,13 +1428,13 @@ def _matching_causatives(
1431
1428
  other_causatives_in_default_panels = []
1432
1429
 
1433
1430
  for causative in matching_causatives:
1434
- hgnc_ids = [gene.get("hgnc_id") for gene in causative.get("genes", [])]
1431
+ hgnc_ids = {gene.get("hgnc_id") for gene in causative.get("genes", [])}
1435
1432
  # Fetch all matching causatives if no causatives_filter defined
1436
1433
  # or only causatives matching the filter:
1437
- if not other_causatives_filter or (set(hgnc_ids) & set(other_causatives_filter)):
1434
+ if not other_causatives_filter or (hgnc_ids & set(other_causatives_filter)):
1438
1435
  other_causatives.append(causative)
1439
1436
  # Only matching causatives in default gene panels:
1440
- if set(hgnc_ids) & set(other_causatives_in_default_panels_filter):
1437
+ if hgnc_ids & set(other_causatives_in_default_panels_filter):
1441
1438
  other_causatives_in_default_panels.append(causative)
1442
1439
 
1443
1440
  return other_causatives, other_causatives_in_default_panels
@@ -120,30 +120,46 @@
120
120
  </div>
121
121
 
122
122
  <div class="card panel-default" >
123
- {% if other_causatives|length > 0%}
124
123
  <div class="row mt-0">
125
- <div class="col-xs-12 col-md-12">{{ matching_causatives(other_causatives, institute, case) }}</div>
126
- </div>
127
- {% endif %}
128
- {% if default_other_causatives|length > 0%}
129
- <div class="row mt-0">
130
- <div class="col-xs-12 col-md-12">{{ matching_causatives(default_other_causatives, institute, case, default=True) }}</div>
131
- </div>
132
- {% endif %}
133
-
134
- {% if managed_variants|length > 0%}
135
- <div class="row mt-0">
136
- <div class="col-sm-12 col-md-12">{{ matching_managed_variants(managed_variants, institute, case) }}</div>
137
- </div>
138
- {% endif %}
139
-
140
- {% if default_managed_variants|length > 0%}
141
- <div class="row mt-0">
142
- <div class="col-sm-12 col-md-12">{{ matching_managed_variants(default_managed_variants, institute, case, default=True) }}</div>
124
+ <div class="col-sm-12 col-md-12 ms-3">
125
+ <div data-bs-toggle='tooltip' class="panel-heading" title="Check if there are any variants in this case
126
+ marked as causative in another case for this institute, or are on the managed variants list.">
127
+ <strong>{% if hide_matching == false %}
128
+ <a href="{{ url_for('cases.case', institute_id=institute._id, case_name=case.display_name, hide_matching='True') }}" class="text-body"><span class="me-1 fa fa-caret-right"></span>Search for matching causatives and managed variants</a>
129
+ {% else %}
130
+ <a href="{{ url_for('cases.case', institute_id=institute._id, case_name=case.display_name, hide_matching='False') }}" class="text-body"><span class="me-1 fa fa-caret-down"></span>Search for matching causatives and managed variants</a>
131
+ {% endif %}
132
+ </strong>
133
+ </div>
134
+ </div>
143
135
  </div>
136
+ {% if hide_matching == false %}
137
+ {% if other_causatives|length > 0 %}
138
+ <div class="row mt-0 ms-3">
139
+ <div class="col-xs-12 col-md-12 ms-3">{{ matching_causatives(other_causatives, institute, case) }}</div>
140
+ </div>
141
+ {% endif %}
142
+ {% if default_other_causatives|length > 0%}
143
+ <div class="row mt-0 ms-3">
144
+ <div class="col-xs-12 col-md-12 ms-3">{{ matching_causatives(default_other_causatives, institute, case, default=True) }}</div>
145
+ </div>
146
+ {% endif %}
147
+ {% if managed_variants|length > 0%}
148
+ <div class="row mt-0 ms-3">
149
+ <div class="col-sm-12 col-md-12 ms-3">{{ matching_managed_variants(managed_variants, institute, case) }}</div>
150
+ </div>
151
+ {% endif %}
152
+ {% if default_managed_variants|length > 0%}
153
+ <div class="row mt-0 ms-3">
154
+ <div class="col-sm-12 col-md-12 ms-3">{{ matching_managed_variants(default_managed_variants, institute, case, default=True) }}</div>
155
+ </div>
156
+ {% endif %}
157
+ {% if other_causatives|length == 0 and default_other_causatives|length == 0 and managed_variants|length == 0 and default_managed_variants|length == 0%}
158
+ <div class="row mt-0 ms-3">
159
+ <div class="col-sm-12 col-md-12 ms-3">No matching causatives or managed variants found</div>
160
+ </div>
161
+ {% endif %}
144
162
  {% endif %}
145
- </div>
146
-
147
163
  <div class="row">
148
164
  <div class="col">{{ causatives_list(causatives, partial_causatives, evaluated_variants, institute, case, manual_rank_options, cancer_tier_options) }}</div>
149
165
  <div class="col">{{ suspects_list(suspects, institute, case, manual_rank_options, cancer_tier_options) }}</div>
@@ -108,7 +108,7 @@
108
108
  {% endif %}
109
109
 
110
110
  <!-- Display mtDNA report for non-cancer cases -->
111
- {% if case.track != "cancer" %}
111
+ {% if case.mtdna_report %}
112
112
  <div href="#" class="bg-dark list-group-item text-white">
113
113
  <div class="d-flex flex-row flex-fill bd-highlight">
114
114
  <div>
@@ -236,11 +236,13 @@
236
236
  <!-- Display and remove added HPO terms -->
237
237
  <div class="row mt-3">
238
238
  <div class="col-12 ms-3">
239
- {% for hpo_term in case.phenotype_terms %}
240
- {{ hpo_item(hpo_term, case) }}
241
- {% else %}
242
- <span class="text-mute">No phenotypes added yet</span>
243
- {% endfor %}
239
+ {% if "phenotype_terms" in case and case.phenotype_terms|length > 0 %}
240
+ {% for hpo_term in case.phenotype_terms %}
241
+ {{ hpo_item(hpo_term, case) }}
242
+ {% endfor %}
243
+ {% else %}
244
+ <span class="text-mute">No phenotypes added yet</span>
245
+ {% endif %}
244
246
  </div>
245
247
  </div>
246
248
 
@@ -251,7 +253,7 @@
251
253
  <input class="ms-3" name="min_match" type="number" min="0" step="1" placeholder="Min matches" style="width:130px"/>
252
254
  {% if config.PHENOMIZER_USERNAME %}
253
255
  <button class="btn btn-secondary btn-sm" type="submit" name="action" value="PHENOMIZER"
254
- {%if case.phenotype_terms|length == 0 %} disabled {%endif%}>Phenomizer</button>
256
+ {% if "phenotype_terms" not in case or case.phenotype_terms|length == 0 %} disabled {% endif %}>Phenomizer</button>
255
257
  {% endif %}
256
258
  </div>
257
259
  <div class="col-1">
@@ -633,8 +633,8 @@
633
633
 
634
634
  {% macro matching_causatives(other_causatives, institute, case, default=False) %}
635
635
  <div data-bs-toggle='tooltip' class="panel-heading" title="If there are any variants in this case
636
- that have been marked as causative in another case for this insitute. {% if default %}Variants in default panels for the case only.{% endif %}">
637
- <strong><a data-bs-toggle="collapse" href="#matchingCausatives{% if default %}Default{% endif %}" class="text-body">Matching causatives {% if default %}in case default panels{% endif %}({{other_causatives|length}})</a></strong>
636
+ matching a causative in another case for this institute. {% if default %}Variants in default panels for the case only.{% endif %}">
637
+ <strong><a data-bs-toggle="collapse" href="#matchingCausatives{% if default %}Default{% endif %}" class="text-body" aria-expanded="false"><span class="collapse-icon me-1"></span>Matching causatives {% if default %}in case default panels{% endif %}({{other_causatives|length}})</a></strong>
638
638
  </div>
639
639
  <ul class="list-group collapse" id="matchingCausatives{% if default %}Default{% endif %}">
640
640
  {% for variant in other_causatives %}
@@ -650,7 +650,7 @@
650
650
  {% macro matching_managed_variants(managed_variants, institute, case, default=False) %}
651
651
  <div data-bs-toggle='tooltip' class="panel-heading" title="Any variants in this case
652
652
  that have been marked as managed. {% if default %}Variants in default panels for the case only.{% endif %}">
653
- <strong><a data-bs-toggle="collapse" class="text-body" href="#matchingManaged{% if default %}Default{% endif %}">Managed variants {% if default %}in case default panels{% endif %}({{managed_variants|length}})</a></strong>
653
+ <strong><a data-bs-toggle="collapse" class="text-body" href="#matchingManaged{% if default %}Default{% endif %}" aria-expanded="false"><span class="collapse-icon me-1"></span>Managed variants {% if default %}in case default panels{% endif %}({{managed_variants|length}})</a></strong>
654
654
  </div>
655
655
  <ul class="list-group">
656
656
  {% for variant in managed_variants %}
@@ -4,6 +4,7 @@ import json
4
4
  import logging
5
5
  import os.path
6
6
  import shutil
7
+ from ast import literal_eval
7
8
  from io import BytesIO
8
9
  from operator import itemgetter
9
10
  from typing import Generator, Optional, Union
@@ -86,7 +87,6 @@ def case(
86
87
  So do case_id, but we still call institute_and_case again to fetch institute
87
88
  and reuse its user access verification.
88
89
  """
89
-
90
90
  if case_id:
91
91
  case_obj = store.case(case_id=case_id, projection={"display_name": 1, "owner": 1})
92
92
 
@@ -102,7 +102,12 @@ def case(
102
102
  flash("Case {} does not exist in database!".format(case_name))
103
103
  return redirect(request.referrer)
104
104
 
105
- data = controllers.case(store, institute_obj, case_obj)
105
+ hide_matching = (
106
+ literal_eval(request.args.get("hide_matching"))
107
+ if request.args.get("hide_matching")
108
+ else True
109
+ )
110
+ data = controllers.case(store, institute_obj, case_obj, hide_matching)
106
111
 
107
112
  return dict(
108
113
  **data,
@@ -271,10 +276,7 @@ def pdf_case_report(institute_id, case_name):
271
276
  store=store, institute_obj=institute_obj, case_obj=case_obj
272
277
  )
273
278
  # add coverage report on the bottom of this report
274
- if (
275
- current_app.config.get("SQLALCHEMY_DATABASE_URI")
276
- and case_obj.get("track", "rare") != "cancer"
277
- ):
279
+ if current_app.config.get("chanjo_report") and case_obj.get("track", "rare") != "cancer":
278
280
  data["coverage_report"] = controllers.coverage_report_contents(
279
281
  request.url_root, institute_obj, case_obj
280
282
  )
@@ -28,7 +28,7 @@ from scout.server.blueprints.variant.utils import (
28
28
  update_variant_case_panels,
29
29
  )
30
30
  from scout.server.blueprints.variants.utils import update_case_panels
31
- from scout.server.extensions import LoqusDB, cloud_tracks, gens
31
+ from scout.server.extensions import LoqusDB, config_igv_tracks, gens
32
32
  from scout.server.links import disease_link, get_variant_links
33
33
  from scout.server.utils import (
34
34
  case_has_alignments,
@@ -142,9 +142,9 @@ def get_igv_tracks(build: str = "37") -> set:
142
142
  # Collect hardcoded tracks, common for all Scout instances
143
143
  for track in IGV_TRACKS.get(build, []):
144
144
  igv_tracks.add(track.get("name"))
145
- # Collect instance-specif cloud public tracks, if available
146
- if hasattr(cloud_tracks, "public_tracks"):
147
- for track in cloud_tracks.public_tracks.get(build, []):
145
+ # Collect instance-specific public tracks, if available
146
+ if hasattr(config_igv_tracks, "tracks"):
147
+ for track in config_igv_tracks.tracks.get(build, []):
148
148
  igv_tracks.add(track.get("name"))
149
149
  return igv_tracks
150
150
 
@@ -372,7 +372,7 @@ def variant(
372
372
  "ACMG_OPTIONS": ACMG_OPTIONS,
373
373
  "case_tag_options": CASE_TAGS,
374
374
  "inherit_palette": INHERITANCE_PALETTE,
375
- "igv_tracks": get_igv_tracks(genome_build),
375
+ "igv_tracks": get_igv_tracks("38" if variant_obj["is_mitochondrial"] else genome_build),
376
376
  "has_rna_tracks": case_has_rna_tracks(case_obj),
377
377
  "gens_info": gens.connection_settings(genome_build),
378
378
  "evaluations": evaluations,
@@ -29,22 +29,27 @@
29
29
  <form action="{{ url_for('variant.variant_acmg', institute_id=institute._id, case_name=case.display_name, variant_id=variant._id) }}" method="POST">
30
30
  <div class="card panel-default mt-3">
31
31
  <div class="card-body">
32
- {% if not evaluation %}
33
- <div class="mt-3 fixed-bottom bg-light border">
34
- <div class="text-center">
35
- {% for option in ACMG_OPTIONS %}
36
- <a id="acmg-{{ option.code }}" class="btn acmg-preview">{{ option.label }}</a>
37
- {% endfor %}
38
- </div>
39
- </div>
40
- <button class="btn btn-primary form-control">Submit</button>
41
- {% else %}
42
- <h4>
32
+ {% if evaluation %}
33
+ <h4>
43
34
  {{ evaluation.classification.label }}
44
35
  <span class="badge bg-info">{{ evaluation.classification.short }}</span>
45
36
  </h4>
46
37
  By {{ evaluation.user_name }} on {{ evaluation.created_at.date() }}
38
+ {% if edit %}
39
+ <br><br>
40
+ <button class="btn btn-primary form-control" data-bs-toggle="tooltip" title="Editing this classification will result in a new classification">Reclassify</button>
41
+ {% endif %}
42
+ {% elif not evaluation %}
43
+ <button class="btn btn-primary form-control">Submit</button>
47
44
  {% endif %}
45
+ <!-- classification preview in the footer-->
46
+ <div class="mt-3 fixed-bottom bg-light border">
47
+ <div class="text-center">
48
+ {% for option in ACMG_OPTIONS %}
49
+ <a id="acmg-{{ option.code }}" class="btn acmg-preview">{{ option.label }}</a>
50
+ {% endfor %}
51
+ </div>
52
+ </div>
48
53
  </div>
49
54
  </div>
50
55
 
@@ -72,14 +77,14 @@
72
77
  {{ criterion.short }}
73
78
  </div>
74
79
  <div class="form-check form-switch">
75
- <input type="checkbox" class="form-check-input" id="checkbox-{{ criterion_code }}" name="criteria" value="{{ criterion_code }}" {{ 'checked' if evaluation and criterion_code in evaluation.criteria }} {{ 'disabled' if evaluation }}>
80
+ <input type="checkbox" class="form-check-input" id="checkbox-{{ criterion_code }}" name="criteria" value="{{ criterion_code }}" {{ 'checked' if evaluation and criterion_code in evaluation.criteria }} {{ 'disabled' if evaluation and edit is false}}>
76
81
  <label class="form-check-label" for="checkbox-{{ criterion_code }}"></label>
77
82
  </div>
78
83
  </div>
79
84
  <div id="comment-{{ criterion_code }}" class="{{ 'collapse' if not (comment or link or modifier) }} mt-2">
80
85
  {% if criterion.documentation %}<div class="row"><span class="me-1 fw-light text-wrap">{{ criterion.documentation | safe}}</span></div>{% endif %}
81
86
  <div class="row">
82
- <select {{ 'disabled' if evaluation }} id="modifier-{{ criterion_code }}" name="modifier-{{ criterion_code }}" class="form-control form-select">
87
+ <select {{ 'disabled' if evaluation and edit is false}} id="modifier-{{ criterion_code }}" name="modifier-{{ criterion_code }}" class="form-control form-select">
83
88
  <option value="" {% if not modifier %}selected{% endif %}>Strength modifier...</option>
84
89
  {% for level in "Strong", "Moderate", "Supporting" %}
85
90
  {% if(level != evidence) %}
@@ -89,10 +94,10 @@
89
94
  </select>
90
95
  </div>
91
96
  <div class="row">
92
- <textarea {{ 'disabled' if evaluation }} class="form-control"
97
+ <textarea {{ 'disabled' if evaluation and edit is false }} class="form-control"
93
98
  name="comment-{{ criterion_code }}" rows="3" placeholder="Comment (optional)">{{ comment }}</textarea>
94
99
  </div>
95
- <input type="url" {{ 'disabled' if evaluation }} class="form-control" name="link-{{ criterion_code }}"
100
+ <input type="url" {{ 'disabled' if evaluation and edit is false }} class="form-control" name="link-{{ criterion_code }}"
96
101
  placeholder="{{ link or 'Supporting link (optional)' }}" value="">
97
102
  </div>
98
103
  </div>
@@ -120,6 +125,11 @@
120
125
  {{ super() }}
121
126
 
122
127
  <script>
128
+
129
+ window.onload=function() {
130
+ update_classification();
131
+ }
132
+
123
133
  $(function () {
124
134
  $('[data-bs-toggle="tooltip"]').tooltip();
125
135
 
@@ -130,7 +140,6 @@
130
140
  } else {
131
141
  el.collapse('hide');
132
142
  }
133
-
134
143
  update_classification()
135
144
  });
136
145
 
@@ -49,14 +49,19 @@
49
49
  {% endif %}
50
50
  </span>
51
51
  </div>
52
- <small class="text-muted">
53
- <form action="{{ url_for('variant.evaluation', evaluation_id=data._id) }}" method="POST">
52
+ <span>
53
+ <small class="text-muted">
54
54
  {{ data.user_name }} on {{ data.created_at.date() }}
55
55
  {% if current_variant %}
56
- <button class="btn btn-xs btn-link">Delete</button>
56
+ <form action="{{ url_for('variant.evaluation', evaluation_id=data._id) }}" method="POST" style="display: inline-block;">
57
+ <button class="btn btn-xs btn-link" >Delete</button>
58
+ </form>
59
+ {% if data.criteria %}
60
+ <a class="btn btn-xs btn-link" href="{{ url_for('variant.evaluation', evaluation_id=data._id, edit=True) }}" data-bs-toggle="tooltip" title="Editing this classification will result in a new classification">Edit</a>
61
+ {% endif %}
57
62
  {% endif %}
58
- </form>
59
- </small>
63
+ </small>
64
+ </span>
60
65
  </li>
61
66
  {% endmacro %}
62
67
 
@@ -90,7 +95,7 @@
90
95
  {% endif %}
91
96
  </div>
92
97
  {% endfor %}
93
- {% if config.SQLALCHEMY_DATABASE_URI %}
98
+ {% if config.chanjo_report %}
94
99
  <div class="d-flex flex-wrap ms-1">
95
100
  {% for gene in variant.genes %}
96
101
  <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) }}">
@@ -13,7 +13,7 @@ from flask import (
13
13
  from flask_login import current_user
14
14
  from markupsafe import Markup
15
15
 
16
- from scout.constants import ACMG_CRITERIA, ACMG_MAP
16
+ from scout.constants import ACMG_CRITERIA, ACMG_MAP, ACMG_OPTIONS
17
17
  from scout.server.blueprints.variant.controllers import check_reset_variant_classification
18
18
  from scout.server.blueprints.variant.controllers import evaluation as evaluation_controller
19
19
  from scout.server.blueprints.variant.controllers import observations, str_variant_reviewer
@@ -308,7 +308,7 @@ def variant_update(institute_id, case_name, variant_id):
308
308
  @variant_bp.route("/evaluations/<evaluation_id>", methods=["GET", "POST"])
309
309
  @templated("variant/acmg.html")
310
310
  def evaluation(evaluation_id):
311
- """Show or delete an ACMG evaluation."""
311
+ """Show, edit or delete an ACMG evaluation."""
312
312
 
313
313
  evaluation_obj = store.get_evaluation(evaluation_id)
314
314
  if evaluation_obj is None:
@@ -328,12 +328,15 @@ def evaluation(evaluation_id):
328
328
  flash("Cleared ACMG classification.", "info")
329
329
 
330
330
  return redirect(link)
331
+
331
332
  return dict(
332
333
  evaluation=evaluation_obj,
334
+ edit=bool(request.args.get("edit")),
333
335
  institute=evaluation_obj["institute"],
334
336
  case=evaluation_obj["case"],
335
337
  variant=evaluation_obj["variant"],
336
338
  CRITERIA=ACMG_CRITERIA,
339
+ ACMG_OPTIONS=ACMG_OPTIONS,
337
340
  )
338
341
 
339
342
 
@@ -1996,7 +1996,7 @@ def activate_case(store, institute_obj, case_obj, current_user):
1996
1996
  store.update_status(institute_obj, case_obj, user_obj, "active", case_link)
1997
1997
 
1998
1998
 
1999
- def reset_all_dimissed(store, institute_obj, case_obj):
1999
+ def reset_all_dismissed(store, institute_obj, case_obj):
2000
2000
  """Reset all dismissed variants for a case.
2001
2001
 
2002
2002
  Args:
@@ -47,7 +47,7 @@ variants_bp = Blueprint(
47
47
  def reset_dismissed(institute_id, case_name):
48
48
  """Reset all dismissed variants for a case"""
49
49
  institute_obj, case_obj = institute_and_case(store, institute_id, case_name)
50
- controllers.reset_all_dimissed(store, institute_obj, case_obj)
50
+ controllers.reset_all_dismissed(store, institute_obj, case_obj)
51
51
  return redirect(request.referrer)
52
52
 
53
53