scout-browser 4.101.0__py3-none-any.whl → 4.103.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. scout/adapter/mongo/case.py +26 -122
  2. scout/adapter/mongo/clinvar.py +98 -32
  3. scout/adapter/mongo/event.py +0 -47
  4. scout/adapter/mongo/hgnc.py +7 -2
  5. scout/adapter/mongo/omics_variant.py +8 -0
  6. scout/adapter/mongo/variant_loader.py +12 -4
  7. scout/build/variant/variant.py +1 -0
  8. scout/commands/load/variants.py +1 -1
  9. scout/commands/update/user.py +87 -49
  10. scout/constants/__init__.py +4 -0
  11. scout/constants/clinvar.py +10 -0
  12. scout/constants/igv_tracks.py +6 -2
  13. scout/constants/phenotype.py +1 -0
  14. scout/constants/variant_tags.py +18 -0
  15. scout/demo/NIST.trgt.stranger.vcf.gz +0 -0
  16. scout/demo/NIST.trgt.stranger.vcf.gz.tbi +0 -0
  17. scout/demo/__init__.py +1 -0
  18. scout/load/hpo.py +8 -2
  19. scout/models/clinvar.py +86 -0
  20. scout/parse/variant/coordinates.py +5 -1
  21. scout/parse/variant/gene.py +5 -9
  22. scout/parse/variant/genotype.py +66 -42
  23. scout/parse/variant/variant.py +2 -0
  24. scout/server/app.py +71 -2
  25. scout/server/blueprints/alignviewers/controllers.py +8 -6
  26. scout/server/blueprints/alignviewers/templates/alignviewers/igv_viewer.html +4 -0
  27. scout/server/blueprints/alignviewers/templates/alignviewers/utils.html +1 -1
  28. scout/server/blueprints/cases/controllers.py +57 -29
  29. scout/server/blueprints/cases/templates/cases/case_report.html +28 -90
  30. scout/server/blueprints/cases/templates/cases/matchmaker.html +1 -1
  31. scout/server/blueprints/cases/templates/cases/phenotype.html +1 -1
  32. scout/server/blueprints/cases/templates/cases/utils.html +34 -53
  33. scout/server/blueprints/cases/views.py +32 -33
  34. scout/server/blueprints/clinvar/controllers.py +235 -54
  35. scout/server/blueprints/clinvar/form.py +38 -1
  36. scout/server/blueprints/clinvar/static/form_style.css +8 -1
  37. scout/server/blueprints/clinvar/templates/clinvar/clinvar_onc_submissions.html +200 -0
  38. scout/server/blueprints/clinvar/templates/clinvar/clinvar_submissions.html +3 -2
  39. scout/server/blueprints/clinvar/templates/clinvar/components.html +198 -0
  40. scout/server/blueprints/clinvar/templates/clinvar/multistep_add_onc_variant.html +187 -0
  41. scout/server/blueprints/clinvar/templates/clinvar/multistep_add_variant.html +9 -348
  42. scout/server/blueprints/clinvar/templates/clinvar/scripts.html +193 -0
  43. scout/server/blueprints/clinvar/views.py +90 -13
  44. scout/server/blueprints/diagnoses/controllers.py +4 -8
  45. scout/server/blueprints/diagnoses/templates/diagnoses/diagnoses.html +1 -1
  46. scout/server/blueprints/diagnoses/templates/diagnoses/disease_term.html +1 -1
  47. scout/server/blueprints/diagnoses/views.py +2 -2
  48. scout/server/blueprints/institutes/controllers.py +148 -75
  49. scout/server/blueprints/institutes/forms.py +1 -0
  50. scout/server/blueprints/institutes/templates/overview/cases.html +1 -1
  51. scout/server/blueprints/institutes/templates/overview/gene_variants.html +15 -6
  52. scout/server/blueprints/institutes/templates/overview/institute_sidebar.html +28 -2
  53. scout/server/blueprints/institutes/templates/overview/utils.html +1 -1
  54. scout/server/blueprints/institutes/views.py +17 -4
  55. scout/server/blueprints/login/controllers.py +2 -1
  56. scout/server/blueprints/login/views.py +5 -2
  57. scout/server/blueprints/mme/templates/mme/mme_submissions.html +2 -2
  58. scout/server/blueprints/omics_variants/templates/omics_variants/outliers.html +2 -2
  59. scout/server/blueprints/omics_variants/views.py +2 -2
  60. scout/server/blueprints/phenotypes/controllers.py +15 -2
  61. scout/server/blueprints/phenotypes/templates/phenotypes/hpo_terms.html +1 -1
  62. scout/server/blueprints/variant/controllers.py +11 -12
  63. scout/server/blueprints/variant/templates/variant/cancer-variant.html +2 -1
  64. scout/server/blueprints/variant/templates/variant/components.html +0 -1
  65. scout/server/blueprints/variant/templates/variant/sv-variant.html +2 -1
  66. scout/server/blueprints/variant/templates/variant/utils.html +1 -1
  67. scout/server/blueprints/variant/templates/variant/variant.html +2 -2
  68. scout/server/blueprints/variant/templates/variant/variant_details.html +100 -84
  69. scout/server/blueprints/variant/utils.py +25 -0
  70. scout/server/blueprints/variants/controllers.py +11 -42
  71. scout/server/blueprints/variants/templates/variants/cancer-variants.html +5 -3
  72. scout/server/blueprints/variants/templates/variants/str-variants.html +4 -1
  73. scout/server/blueprints/variants/templates/variants/sv-variants.html +3 -3
  74. scout/server/blueprints/variants/templates/variants/utils.html +4 -0
  75. scout/server/blueprints/variants/templates/variants/variants.html +4 -4
  76. scout/server/blueprints/variants/views.py +9 -8
  77. scout/server/config.py +3 -0
  78. scout/server/extensions/beacon_extension.py +7 -2
  79. scout/server/extensions/clinvar_extension.py +2 -2
  80. scout/server/templates/bootstrap_global.html +11 -1
  81. scout/server/templates/layout.html +6 -1
  82. scout/server/utils.py +24 -3
  83. {scout_browser-4.101.0.dist-info → scout_browser-4.103.0.dist-info}/METADATA +1 -1
  84. {scout_browser-4.101.0.dist-info → scout_browser-4.103.0.dist-info}/RECORD +87 -81
  85. {scout_browser-4.101.0.dist-info → scout_browser-4.103.0.dist-info}/WHEEL +0 -0
  86. {scout_browser-4.101.0.dist-info → scout_browser-4.103.0.dist-info}/entry_points.txt +0 -0
  87. {scout_browser-4.101.0.dist-info → scout_browser-4.103.0.dist-info}/licenses/LICENSE +0 -0
@@ -18,6 +18,7 @@ Uses 'DV' to describe number of paired ends that supports the event and
18
18
 
19
19
  """
20
20
 
21
+ import ast
21
22
  import logging
22
23
  from typing import Dict, List, Optional, Tuple, Union
23
24
 
@@ -101,7 +102,7 @@ def parse_genotype(variant, ind, pos):
101
102
  (_, mc_alt) = _parse_format_entry_trgt_mc(variant, pos)
102
103
  gt_call["alt_mc"] = mc_alt
103
104
 
104
- (sd_ref, sd_alt) = _parse_format_entry(variant, pos, "SD", float)
105
+ (sd_ref, sd_alt) = _parse_format_entry(variant, pos, "SD", int)
105
106
  (ap_ref, ap_alt) = _parse_format_entry(variant, pos, "AP", float)
106
107
  (am_ref, am_alt) = _parse_format_entry(variant, pos, "AM", float)
107
108
 
@@ -122,6 +123,7 @@ def parse_genotype(variant, ind, pos):
122
123
  spanning_alt,
123
124
  flanking_alt,
124
125
  inrepeat_alt,
126
+ sd_alt,
125
127
  clip5_alt,
126
128
  clip3_alt,
127
129
  )
@@ -135,8 +137,10 @@ def parse_genotype(variant, ind, pos):
135
137
  spanning_ref,
136
138
  flanking_ref,
137
139
  inrepeat_ref,
140
+ sd_ref,
138
141
  spanning_mei_ref,
139
142
  )
143
+
140
144
  gt_call["ref_depth"] = ref_depth
141
145
  gt_call["read_depth"] = get_read_depth(variant, pos, alt_depth, ref_depth)
142
146
  gt_call["alt_frequency"] = get_alt_frequency(variant, pos)
@@ -202,7 +206,7 @@ def get_ffpm_info(variant: cyvcf2.Variant, pos: Dict[str, int]) -> Optional[int]
202
206
  pass
203
207
 
204
208
 
205
- def get_paired_ends(variant, pos):
209
+ def get_paired_ends(variant: cyvcf2.Variant, pos: int) -> tuple:
206
210
  """Get paired ends"""
207
211
  # SV specific
208
212
  paired_end_alt = None
@@ -224,9 +228,10 @@ def get_paired_ends(variant, pos):
224
228
  values = variant.format("PR")[pos]
225
229
  try:
226
230
  alt_value = int(values[1])
227
- ref_value = int(values[0])
228
231
  if alt_value >= 0:
229
232
  paired_end_alt = alt_value
233
+
234
+ ref_value = int(values[0])
230
235
  if ref_value >= 0:
231
236
  paired_end_ref = ref_value
232
237
  except ValueError as _ignore_error:
@@ -317,32 +322,30 @@ def get_read_depth(variant, pos, alt_depth, ref_depth):
317
322
 
318
323
 
319
324
  def get_ref_depth(
320
- variant,
321
- pos,
322
- paired_end_ref,
323
- split_read_ref,
324
- spanning_ref,
325
- flanking_ref,
326
- inrepeat_ref,
327
- spanning_mei_ref,
328
- ):
325
+ variant: cyvcf2.Variant,
326
+ pos: int,
327
+ paired_end_ref: int,
328
+ split_read_ref: int,
329
+ spanning_ref: int,
330
+ flanking_ref: int,
331
+ inrepeat_ref: int,
332
+ sd_ref: int,
333
+ spanning_mei_ref: int,
334
+ ) -> int:
329
335
  """Get reference read depth"""
330
336
  ref_depth = int(variant.gt_ref_depths[pos])
331
337
  if ref_depth != -1:
332
338
  return ref_depth
333
339
 
334
340
  REF_ITEMS_LIST: List[tuple] = [
341
+ (sd_ref,),
335
342
  (paired_end_ref, split_read_ref),
336
343
  (spanning_ref, flanking_ref, inrepeat_ref),
337
344
  ]
338
345
 
339
- for list in REF_ITEMS_LIST:
340
- if all(item is None for item in list):
341
- continue
342
- ref_depth = 0
343
- for item in list:
344
- if item:
345
- ref_depth += item
346
+ for items in REF_ITEMS_LIST:
347
+ if any(item is not None for item in items):
348
+ ref_depth = sum(item for item in items if item)
346
349
 
347
350
  if spanning_mei_ref:
348
351
  ref_depth += spanning_mei_ref
@@ -350,16 +353,17 @@ def get_ref_depth(
350
353
 
351
354
 
352
355
  def get_alt_depth(
353
- variant,
354
- pos,
355
- paired_end_alt,
356
- split_read_alt,
357
- spanning_alt,
358
- flanking_alt,
359
- inrepeat_alt,
360
- clip5_alt,
361
- clip3_alt,
362
- ):
356
+ variant: cyvcf2.Variant,
357
+ pos: int,
358
+ paired_end_alt: int,
359
+ split_read_alt: int,
360
+ spanning_alt: int,
361
+ flanking_alt: int,
362
+ inrepeat_alt: int,
363
+ sd_alt: int,
364
+ clip5_alt: int,
365
+ clip3_alt: int,
366
+ ) -> int:
363
367
  """Get alternative read depth"""
364
368
  alt_depth = int(variant.gt_alt_depths[pos])
365
369
  if alt_depth != -1:
@@ -369,19 +373,15 @@ def get_alt_depth(
369
373
  alt_depth = int(variant.format("VD")[pos][0])
370
374
 
371
375
  ALT_ITEMS_LIST: List[tuple] = [
376
+ (sd_alt,),
372
377
  (paired_end_alt, split_read_alt),
373
378
  (clip5_alt, clip3_alt),
374
379
  (spanning_alt, flanking_alt, inrepeat_alt),
375
380
  ]
376
381
 
377
- for list in ALT_ITEMS_LIST:
378
- if all(item is None for item in list):
379
- continue
380
- alt_depth = 0
381
- for item in list:
382
- if item:
383
- alt_depth += item
384
-
382
+ for items in ALT_ITEMS_LIST:
383
+ if any(item is not None for item in items):
384
+ alt_depth = sum(item for item in items if item)
385
385
  return alt_depth
386
386
 
387
387
 
@@ -430,7 +430,13 @@ def _parse_format_entry(
430
430
  alt = None
431
431
  if format_entry_name in variant.FORMAT:
432
432
  try:
433
- values = split_values(variant.format(format_entry_name)[pos])
433
+ requested_format_entry = variant.format(format_entry_name)[pos]
434
+
435
+ values = (
436
+ split_values(requested_format_entry)
437
+ if type(requested_format_entry) is str
438
+ else requested_format_entry
439
+ )
434
440
 
435
441
  ref_value = None
436
442
  alt_value = None
@@ -440,15 +446,31 @@ def _parse_format_entry(
440
446
  alt_value = (number_format)(values[1])
441
447
  if len(values) == 1:
442
448
  alt_value = (number_format)(values[0])
443
- if ref_value >= 0:
449
+ if ref_value and ref_value >= 0:
444
450
  ref = ref_value
445
- if alt_value >= 0:
451
+ if alt_value and alt_value >= 0:
446
452
  alt = alt_value
447
453
  except (ValueError, TypeError) as _ignore_error:
448
454
  pass
449
455
  return (ref, alt)
450
456
 
451
457
 
458
+ def _get_pathologic_struc(variant: cyvcf2.Variant) -> Optional[list]:
459
+ """Check for a PathologicStruc on the variant. If not present, return None.
460
+ If present, and in string format, convert to a list of ints.
461
+ If it is already parsed to a list by a later improvement in the parser,
462
+ simply return it.
463
+ """
464
+
465
+ pathologic_struc_entry = variant.INFO.get("PathologicStruc", None)
466
+ if not pathologic_struc_entry:
467
+ return pathologic_struc_entry
468
+ if type(pathologic_struc_entry) is str:
469
+ return ast.literal_eval(pathologic_struc_entry)
470
+ if type(pathologic_struc_entry) is list:
471
+ return pathologic_struc_entry
472
+
473
+
452
474
  def _parse_format_entry_trgt_mc(variant: cyvcf2.Variant, pos: int):
453
475
  """Parse genotype entry for TRGT FORMAT MC
454
476
 
@@ -477,14 +499,16 @@ def _parse_format_entry_trgt_mc(variant: cyvcf2.Variant, pos: int):
477
499
  if allele == 0:
478
500
  ref_idx = idx
479
501
 
480
- pathologic_struc = variant.INFO.get("PathologicStruc", None)
481
- pathologic_counts = 0
502
+ pathologic_struc = _get_pathologic_struc(variant)
503
+
482
504
  for idx, allele in enumerate(mc.split(",")):
505
+
483
506
  mcs = allele.split("_")
484
507
 
485
508
  if len(mcs) > 1:
486
509
  pathologic_mcs = pathologic_struc or range(len(mcs))
487
510
 
511
+ pathologic_counts = 0
488
512
  for index, count in enumerate(mcs):
489
513
  if index in pathologic_mcs:
490
514
  pathologic_counts += int(count)
@@ -82,6 +82,8 @@ def parse_variant(
82
82
  variant_type=variant_type,
83
83
  ),
84
84
  "rank_score": parse_rank_score(variant.INFO.get("RankScore", ""), genmod_key) or 0,
85
+ "norm_rank_score": parse_rank_score(variant.INFO.get("RankScoreNormalized", ""), genmod_key)
86
+ or 0,
85
87
  "genetic_models": parse_genetic_models(variant.INFO.get("GeneticModels"), genmod_key),
86
88
  "str_swegen_mean": call_safe(float, variant.INFO.get("SweGenMean")),
87
89
  "str_swegen_std": call_safe(float, variant.INFO.get("SweGenStd")),
scout/server/app.py CHANGED
@@ -4,7 +4,7 @@ import logging
4
4
  import os
5
5
  import re
6
6
  from datetime import timedelta
7
- from typing import Dict, Union
7
+ from typing import Dict, List, Optional, Union
8
8
  from urllib.parse import parse_qsl, unquote, urlsplit
9
9
 
10
10
  from flask import Flask, redirect, request, url_for
@@ -14,7 +14,11 @@ from markdown import markdown as python_markdown
14
14
  from markupsafe import Markup
15
15
 
16
16
  from scout import __version__
17
- from scout.constants import SPIDEX_HUMAN
17
+ from scout.constants import (
18
+ REVEL_SCORE_LABEL_COLOR_MAP,
19
+ SPIDEX_HUMAN,
20
+ SPLICEAI_SCORE_LABEL_COLOR_MAP,
21
+ )
18
22
  from scout.log import init_log
19
23
 
20
24
  from . import extensions
@@ -207,6 +211,9 @@ def register_blueprints(app):
207
211
 
208
212
 
209
213
  def register_filters(app):
214
+ """Creates methods that can be invoked from jinja2 or views/controllers, given that they are included app.custom_filters."""
215
+ app.custom_filters = type("CustomFilters", (), {})() # Create an empty object as namespace
216
+
210
217
  @app.template_filter()
211
218
  def human_longint(value: Union[int, str]) -> str:
212
219
  """Convert a long integers int or string representation into a human easily readable number."""
@@ -226,6 +233,27 @@ def register_filters(app):
226
233
  return "medium"
227
234
  return "high"
228
235
 
236
+ @app.template_filter()
237
+ def get_label_or_color_by_score(
238
+ score: float,
239
+ map: str,
240
+ map_key: str,
241
+ ) -> str:
242
+ """Return a label or color for a given score based on predefined score ranges from the provided items_map."""
243
+ SCORE_ITEM_MAPS = {
244
+ "revel": REVEL_SCORE_LABEL_COLOR_MAP,
245
+ "spliceai": SPLICEAI_SCORE_LABEL_COLOR_MAP,
246
+ }
247
+ for (low, high), info in SCORE_ITEM_MAPS[map].items():
248
+ if low <= score <= high:
249
+ return info[map_key]
250
+
251
+ @app.template_filter()
252
+ def l2fc_2_fc(l2fc: float) -> float:
253
+ """Converts Log2 fold change to fold change."""
254
+ fc = 2 ** abs(l2fc)
255
+ return fc if l2fc >= 0 else -1 / fc
256
+
229
257
  @app.template_filter()
230
258
  def human_decimal(number, ndigits=4):
231
259
  """Return a standard representation of a decimal number.
@@ -281,6 +309,33 @@ def register_filters(app):
281
309
  return "COSM" + str(cosmicId)
282
310
  return cosmicId
283
311
 
312
+ @app.template_filter()
313
+ def format_variant_canonical_transcripts(variant: dict) -> List[str]:
314
+ """Formats canonical transcripts for all genes in a variant."""
315
+
316
+ lines = set()
317
+ genes = variant.get("genes") or []
318
+
319
+ for gene in genes:
320
+ transcripts = gene.get("transcripts") or []
321
+ for tx in transcripts:
322
+ if not tx.get("is_canonical"):
323
+ continue
324
+ canonical_tx = tx.get("transcript_id")
325
+ protein = tx.get("protein_sequence_name")
326
+ line_components = [f"{canonical_tx} ({gene.get('hgnc_symbol', '')})"]
327
+ hgvs = gene.get("hgvs_identifier")
328
+ if hgvs:
329
+ line_components.append(hgvs)
330
+ if protein:
331
+ line_components.append(protein)
332
+
333
+ lines.add(" ".join(line_components))
334
+
335
+ return list(lines)
336
+
337
+ app.custom_filters.format_variant_canonical_transcripts = format_variant_canonical_transcripts
338
+
284
339
  @app.template_filter()
285
340
  def upper_na(string):
286
341
  """
@@ -306,6 +361,20 @@ def register_filters(app):
306
361
  """Returns a list after removing any None values from it."""
307
362
  return [item for item in in_list if item is not None]
308
363
 
364
+ @app.template_filter()
365
+ def spliceai_max(values) -> Optional[float]:
366
+ """Returns a list of SpliceAI values, extracting floats only from values like 'score:0.23'."""
367
+ float_values = []
368
+ for value in values:
369
+ if isinstance(value, str):
370
+ if ":" in value: # Variant hits multiple genes
371
+ value = value.split(":")[1].strip()
372
+ if value in [None, "-", "None"]:
373
+ continue
374
+ float_values.append(float(value))
375
+ if float_values:
376
+ return max(float_values)
377
+
309
378
 
310
379
  def register_tests(app):
311
380
  @app.template_test("existing")
@@ -8,7 +8,11 @@ from flask_login import current_user
8
8
 
9
9
  from scout.constants import CASE_SPECIFIC_TRACKS, HUMAN_REFERENCE, IGV_TRACKS
10
10
  from scout.server.extensions import config_igv_tracks, store
11
- from scout.server.utils import case_append_alignments, find_index
11
+ from scout.server.utils import (
12
+ case_append_alignments,
13
+ find_index,
14
+ get_case_genome_build,
15
+ )
12
16
  from scout.utils.ensembl_rest_clients import EnsemblRestApiClient
13
17
 
14
18
  LOG = logging.getLogger(__name__)
@@ -88,7 +92,8 @@ def make_igv_tracks(
88
92
  display_obj["locus"] = "chr{0}:{1}-{2}".format(chromosome, start, stop)
89
93
 
90
94
  # Set genome build for displaying alignments:
91
- if "38" in str(case_obj.get("genome_build", "37")) or chromosome == "M":
95
+
96
+ if get_case_genome_build(case_obj) == "38" or chromosome == "M":
92
97
  build = "38"
93
98
  else:
94
99
  build = "37"
@@ -137,10 +142,7 @@ def make_sashimi_tracks(
137
142
  """
138
143
 
139
144
  locus = "All"
140
-
141
- build = "38"
142
- if "37" in str(case_obj.get("rna_genome_build", "38")):
143
- build = "37"
145
+ build = "37" if "37" in str(case_obj.get("rna_genome_build", "38")) else "38"
144
146
 
145
147
  if variant_id:
146
148
  variant_obj = store.variant(document_id=variant_id)
@@ -35,6 +35,8 @@
35
35
  $(document).ready(function () {
36
36
  var div = $("#igvDiv")[0],
37
37
  options = {
38
+ loadDefaultGenomes: false,
39
+ genomeList: "https://raw.githubusercontent.com/igvteam/igv-data/main/genomes/web/genomes.json",
38
40
  showNavigation: true,
39
41
  showRuler: true,
40
42
  {% if display_center_guide %}
@@ -117,6 +119,8 @@
117
119
  indexURL: "{{ url_for('alignviewers.remote_static', file=track.indexURL) }}",
118
120
  sourceType: "file",
119
121
  groupBy: "tag:HP",
122
+ showInsertionText: true,
123
+ showDeletionText: true,
120
124
  showSoftClips: {{track.show_soft_clips | lower }},
121
125
  format: "{{ track.format }}",
122
126
  height: "{{track.height}}"
@@ -1,5 +1,5 @@
1
1
  {% macro igv_script() %}
2
2
  <link rel="shortcut icon" href="//igv.org/web/img/favicon.ico">
3
3
  <!-- IGV JS-->
4
- <script src="https://cdn.jsdelivr.net/npm/igv@3.2.0/dist/igv.min.js" integrity="sha512-MHnbGQeONlQXyEs6PgiW2bhwywJW5IwUnRKfQKrPaVSrzopctBTU1VtOiEXMf/ZPBk47eFimlVRxdff+sdsyAg==" crossorigin="anonymous"></script>
4
+ <script src="https://cdn.jsdelivr.net/npm/igv@3.3.0/dist/igv.min.js" integrity="sha512-U3drIflKJksG0+kiVuqYrQaI9SsloPhKfISeZr8bzVpO/oRFz7hE1vdJirvDyYIfv51CSucKJgcLAdupqitBcQ==" crossorigin="anonymous"></script>
5
5
  {% endmacro %}
@@ -22,6 +22,7 @@ from scout.constants import (
22
22
  CUSTOM_CASE_REPORTS,
23
23
  DATE_DAY_FORMATTER,
24
24
  GENOME_REGION,
25
+ HPO_LINK_URL,
25
26
  INHERITANCE_PALETTE,
26
27
  MITODEL_HEADER,
27
28
  MT_COV_STATS_HEADER,
@@ -66,6 +67,7 @@ from scout.server.utils import (
66
67
  case_has_mt_alignments,
67
68
  case_has_mtdna_report,
68
69
  case_has_rna_tracks,
70
+ get_case_genome_build,
69
71
  get_case_mito_chromosome,
70
72
  institute_and_case,
71
73
  )
@@ -157,7 +159,12 @@ def coverage_report_contents(base_url, institute_obj, case_obj):
157
159
  return html_body_content
158
160
 
159
161
 
160
- def _populate_case_groups(store, case_obj, case_groups, case_group_label):
162
+ def _populate_case_groups(
163
+ store: MongoAdapter,
164
+ case_obj: dict,
165
+ case_groups: Dict[str, List[str]],
166
+ case_group_label: Dict[str, str],
167
+ ):
161
168
  """Case groups allow display of information about user linked cases together on one case.
162
169
  Notably variantS lists show shared annotations and alignment views show all alignments
163
170
  available for the group.
@@ -178,20 +185,21 @@ def _populate_case_groups(store, case_obj, case_groups, case_group_label):
178
185
  case_group_label[group] = store.case_group_label(group)
179
186
 
180
187
 
181
- def _get_partial_causatives(store: MongoAdapter, case_obj: Dict) -> List[Dict]:
182
- """Return any partial causatives a case has, populated as causative objs.
183
- Args:
184
- store(adapter.MongoAdapter)
185
- case_obj(models.Case)
186
- Returns:
187
- partial(list(dict))
188
+ def _get_partial_causatives(store: MongoAdapter, institute_obj: dict, case_obj: dict) -> List[Dict]:
189
+ """Check for partial causatives and associated phenotypes.
190
+ Return any partial causatives a case has, populated as causative objs.
188
191
  """
189
192
 
190
193
  partial_causatives = []
191
194
  if case_obj.get("partial_causatives"):
192
195
  for var_id, values in case_obj["partial_causatives"].items():
196
+ variant_obj = store.variant(var_id)
197
+ if variant_obj:
198
+ decorated_variant_obj = _get_decorated_var(
199
+ store, var_obj=variant_obj, institute_obj=institute_obj, case_obj=case_obj
200
+ )
193
201
  causative_obj = {
194
- "variant": store.variant(var_id) or var_id,
202
+ "variant": decorated_variant_obj or var_id,
195
203
  "disease_terms": values.get("diagnosis_phenotypes"),
196
204
  "hpo_terms": values.get("phenotype_terms"),
197
205
  }
@@ -287,7 +295,7 @@ def bionano_case(store, institute_obj, case_obj) -> Dict:
287
295
  return data
288
296
 
289
297
 
290
- def sma_case(store, institute_obj, case_obj):
298
+ def sma_case(store: MongoAdapter, institute_obj: dict, case_obj: dict) -> dict:
291
299
  """Preprocess a case for tabular view, SMA."""
292
300
 
293
301
  _populate_case_individuals(case_obj)
@@ -299,11 +307,31 @@ def sma_case(store, institute_obj, case_obj):
299
307
  "case": case_obj,
300
308
  "comments": store.events(institute_obj, case=case_obj, comments=True),
301
309
  "events": _get_events(store, institute_obj, case_obj),
302
- "region": GENOME_REGION[case_obj.get("genome_build", "38")],
310
+ "region": GENOME_REGION[get_case_genome_build(case_obj)],
303
311
  }
304
312
  return data
305
313
 
306
314
 
315
+ def _get_suspects_or_causatives(
316
+ store: MongoAdapter, institute_obj: dict, case_obj: dict, kind: str = "suspects"
317
+ ) -> list:
318
+ """Fetch the variant objects for suspects and causatives and decorate them.
319
+ If no longer available, append variant_id instead."""
320
+
321
+ marked_vars = []
322
+ for variant_id in case_obj.get(kind, []):
323
+ variant_obj = store.variant(variant_id)
324
+ if variant_obj:
325
+ marked_vars.append(
326
+ _get_decorated_var(
327
+ store, var_obj=variant_obj, institute_obj=institute_obj, case_obj=case_obj
328
+ )
329
+ )
330
+ else:
331
+ marked_vars.append(variant_id)
332
+ return marked_vars
333
+
334
+
307
335
  def case(
308
336
  store: MongoAdapter, institute_obj: dict, case_obj: dict, hide_matching: bool = True
309
337
  ) -> dict:
@@ -331,25 +359,19 @@ def case(
331
359
  case_group_label = {}
332
360
  _populate_case_groups(store, case_obj, case_groups, case_group_label)
333
361
 
334
- # Fetch the variant objects for suspects and causatives
335
- suspects = [
336
- store.variant(variant_id) or variant_id for variant_id in case_obj.get("suspects", [])
337
- ]
362
+ suspects = _get_suspects_or_causatives(store, institute_obj, case_obj, "suspects")
338
363
  _populate_assessments(suspects)
339
- causatives = [
340
- store.variant(variant_id) or variant_id for variant_id in case_obj.get("causatives", [])
341
- ]
364
+
365
+ causatives = _get_suspects_or_causatives(store, institute_obj, case_obj, "causatives")
342
366
  _populate_assessments(causatives)
343
367
 
344
- # get evaluated variants
345
368
  evaluated_variants = store.evaluated_variants(case_obj["_id"], case_obj["owner"])
346
369
  _populate_assessments(evaluated_variants)
347
370
 
348
- # check for partial causatives and associated phenotypes
349
- partial_causatives = _get_partial_causatives(store, case_obj)
371
+ partial_causatives = _get_partial_causatives(store, institute_obj, case_obj)
350
372
  _populate_assessments(partial_causatives)
351
373
 
352
- case_obj["clinvar_variants"] = store.case_to_clinVars(case_obj["_id"])
374
+ case_obj["clinvar_variants"] = store.case_to_clinvars(case_obj["_id"])
353
375
 
354
376
  # check for variants submitted to clinVar but not present in suspects for the case
355
377
  clinvar_variants_not_in_suspects = [
@@ -367,9 +389,7 @@ def case(
367
389
  for hpo_term in itertools.chain(
368
390
  case_obj.get("phenotype_groups") or [], case_obj.get("phenotype_terms") or []
369
391
  ):
370
- hpo_term["hpo_link"] = "http://hpo.jax.org/app/browse/term/{}".format(
371
- hpo_term["phenotype_id"]
372
- )
392
+ hpo_term["hpo_link"] = f"{HPO_LINK_URL}{hpo_term['phenotype_id']}"
373
393
 
374
394
  _set_rank_model_links(case_obj)
375
395
 
@@ -471,7 +491,7 @@ def case(
471
491
  "tissue_types": SAMPLE_SOURCE,
472
492
  "report_types": CUSTOM_CASE_REPORTS,
473
493
  "mme_nodes": matchmaker.connected_nodes,
474
- "gens_info": gens.connection_settings(case_obj.get("genome_build")),
494
+ "gens_info": gens.connection_settings(get_case_genome_build(case_obj)),
475
495
  "display_rerunner": rerunner.connection_settings.get("display", False),
476
496
  "hide_matching": hide_matching,
477
497
  "audits": store.case_events_by_verb(
@@ -676,7 +696,9 @@ def case_report_variants(store: MongoAdapter, case_obj: dict, institute_obj: dic
676
696
  add_bayesian_acmg_classification(var_obj)
677
697
  add_bayesian_ccv_classification(var_obj)
678
698
  evaluated_variants_by_type[eval_category].append(
679
- _get_decorated_var(var_obj=var_obj, institute_obj=institute_obj, case_obj=case_obj)
699
+ _get_decorated_var(
700
+ store, var_obj=var_obj, institute_obj=institute_obj, case_obj=case_obj
701
+ )
680
702
  )
681
703
 
682
704
  for var_obj in store.evaluated_variants(
@@ -689,7 +711,9 @@ def case_report_variants(store: MongoAdapter, case_obj: dict, institute_obj: dic
689
711
  data["variants"] = evaluated_variants_by_type
690
712
 
691
713
 
692
- def _get_decorated_var(var_obj: dict, institute_obj: dict, case_obj: dict) -> dict:
714
+ def _get_decorated_var(
715
+ store: MongoAdapter, var_obj: dict, institute_obj: dict, case_obj: dict
716
+ ) -> dict:
693
717
  """Decorate a variant object for display using the variant controller"""
694
718
  return variant_decorator(
695
719
  store=store,
@@ -719,7 +743,9 @@ def _append_evaluated_variant_by_type(
719
743
  add_bayesian_ccv_classification(var_obj)
720
744
 
721
745
  evaluated_variants_by_type[eval_category].append(
722
- _get_decorated_var(var_obj=var_obj, institute_obj=institute_obj, case_obj=case_obj)
746
+ _get_decorated_var(
747
+ store, var_obj=var_obj, institute_obj=institute_obj, case_obj=case_obj
748
+ )
723
749
  )
724
750
 
725
751
 
@@ -765,6 +791,7 @@ def case_report_content(store: MongoAdapter, institute_obj: dict, case_obj: dict
765
791
  data["genetic_models"] = dict(GENETIC_MODELS)
766
792
  data["report_created_at"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
767
793
  data["current_scout_version"] = __version__
794
+ data["hpo_link_url"] = HPO_LINK_URL
768
795
 
769
796
  case_report_variants(store, case_obj, institute_obj, data)
770
797
 
@@ -1395,6 +1422,7 @@ def matchmaker_matches(request, institute_id, case_name):
1395
1422
  pat_matches = parse_matches(patient_id, server_resp["content"]["matches"])
1396
1423
  matches[patient_id] = pat_matches
1397
1424
 
1425
+ data["hpo_link_url"] = HPO_LINK_URL
1398
1426
  data["matches"] = matches
1399
1427
  return data
1400
1428