scout-browser 4.82.1__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.
- scout/__version__.py +1 -1
- scout/adapter/client.py +1 -0
- scout/adapter/mongo/base.py +0 -1
- scout/adapter/mongo/case.py +15 -37
- scout/adapter/mongo/case_events.py +98 -2
- scout/adapter/mongo/hgnc.py +39 -22
- scout/adapter/mongo/institute.py +3 -9
- scout/adapter/mongo/panel.py +2 -1
- scout/adapter/mongo/variant.py +3 -2
- scout/adapter/mongo/variant_loader.py +92 -79
- scout/commands/base.py +1 -0
- scout/commands/update/case.py +10 -10
- scout/commands/update/individual.py +6 -1
- scout/constants/file_types.py +4 -0
- scout/load/__init__.py +0 -1
- scout/load/all.py +3 -4
- scout/load/panel.py +8 -4
- scout/load/setup.py +1 -0
- scout/models/case/case_loading_models.py +6 -16
- scout/parse/case.py +0 -1
- scout/parse/disease_terms.py +1 -0
- scout/parse/omim.py +1 -0
- scout/parse/panel.py +40 -15
- scout/resources/__init__.py +3 -0
- scout/server/app.py +4 -50
- scout/server/blueprints/alignviewers/controllers.py +15 -17
- scout/server/blueprints/alignviewers/templates/alignviewers/igv_viewer.html +13 -3
- scout/server/blueprints/alignviewers/views.py +10 -15
- scout/server/blueprints/cases/controllers.py +70 -73
- scout/server/blueprints/cases/templates/cases/case.html +37 -21
- scout/server/blueprints/cases/templates/cases/case_bionano.html +3 -24
- scout/server/blueprints/cases/templates/cases/case_report.html +5 -3
- scout/server/blueprints/cases/templates/cases/case_sma.html +2 -13
- scout/server/blueprints/cases/templates/cases/collapsible_actionbar.html +1 -1
- scout/server/blueprints/cases/templates/cases/individuals_table.html +2 -12
- scout/server/blueprints/cases/templates/cases/phenotype.html +8 -6
- scout/server/blueprints/cases/templates/cases/utils.html +20 -3
- scout/server/blueprints/cases/views.py +8 -6
- scout/server/blueprints/variant/controllers.py +5 -5
- scout/server/blueprints/variant/templates/variant/acmg.html +25 -16
- scout/server/blueprints/variant/templates/variant/components.html +11 -6
- scout/server/blueprints/variant/views.py +5 -2
- scout/server/blueprints/variants/controllers.py +3 -5
- scout/server/blueprints/variants/templates/variants/str-variants.html +1 -1
- scout/server/blueprints/variants/views.py +1 -1
- scout/server/config.py +16 -4
- scout/server/extensions/__init__.py +4 -2
- scout/server/extensions/beacon_extension.py +1 -0
- scout/server/extensions/chanjo_extension.py +58 -0
- scout/server/extensions/phenopacket_extension.py +1 -0
- scout/server/static/bs_styles.css +18 -0
- scout/server/utils.py +16 -2
- scout/utils/acmg.py +33 -20
- scout/utils/track_resources.py +70 -0
- {scout_browser-4.82.1.dist-info → scout_browser-4.83.dist-info}/METADATA +1 -1
- {scout_browser-4.82.1.dist-info → scout_browser-4.83.dist-info}/RECORD +60 -60
- scout/load/case.py +0 -36
- scout/utils/cloud_resources.py +0 -61
- {scout_browser-4.82.1.dist-info → scout_browser-4.83.dist-info}/LICENSE +0 -0
- {scout_browser-4.82.1.dist-info → scout_browser-4.83.dist-info}/WHEEL +0 -0
- {scout_browser-4.82.1.dist-info → scout_browser-4.83.dist-info}/entry_points.txt +0 -0
- {scout_browser-4.82.1.dist-info → scout_browser-4.83.dist-info}/top_level.txt +0 -0
@@ -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
|
33
|
-
|
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
|
-
<
|
53
|
-
<
|
52
|
+
<span>
|
53
|
+
<small class="text-muted">
|
54
54
|
{{ data.user_name }} on {{ data.created_at.date() }}
|
55
55
|
{% if current_variant %}
|
56
|
-
|
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
|
-
</
|
59
|
-
</
|
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.
|
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
|
|
@@ -941,7 +941,6 @@ def parse_variant(
|
|
941
941
|
variant_obj["end_chrom"] = variant_obj["chromosome"]
|
942
942
|
|
943
943
|
# common motif count for STR variants
|
944
|
-
|
945
944
|
variant_obj["str_mc"] = get_str_mc(variant_obj)
|
946
945
|
|
947
946
|
# variant level links shown on variants page
|
@@ -970,7 +969,6 @@ def get_str_mc(variant_obj: dict) -> Optional[int]:
|
|
970
969
|
from the variant FORMAT field, or as a number given in the ALT on the form
|
971
970
|
'<STR123>'.
|
972
971
|
"""
|
973
|
-
|
974
972
|
alt_mc = None
|
975
973
|
if variant_obj["alternative"] == ".":
|
976
974
|
return alt_mc
|
@@ -982,9 +980,9 @@ def get_str_mc(variant_obj: dict) -> Optional[int]:
|
|
982
980
|
if alt_mc:
|
983
981
|
return alt_mc
|
984
982
|
|
985
|
-
alt_num = NUM.
|
983
|
+
alt_num = NUM.search(variant_obj["alternative"])
|
986
984
|
if alt_num:
|
987
|
-
alt_mc = int(alt_num)
|
985
|
+
alt_mc = int(alt_num.group())
|
988
986
|
return alt_mc
|
989
987
|
|
990
988
|
return None
|
@@ -1998,7 +1996,7 @@ def activate_case(store, institute_obj, case_obj, current_user):
|
|
1998
1996
|
store.update_status(institute_obj, case_obj, user_obj, "active", case_link)
|
1999
1997
|
|
2000
1998
|
|
2001
|
-
def
|
1999
|
+
def reset_all_dismissed(store, institute_obj, case_obj):
|
2002
2000
|
"""Reset all dismissed variants for a case.
|
2003
2001
|
|
2004
2002
|
Args:
|
@@ -93,7 +93,7 @@
|
|
93
93
|
<td class="str-link">{{ str_locus_info(variant) }}</td>
|
94
94
|
<td class="text-end">{{ variant.str_display_ru or variant.str_ru or variant.reference }}</td>
|
95
95
|
<td class="text-end"><b><span data-bs-toggle="tooltip" title="{{ variant.alternative }}">{{ variant.str_mc }}</span></b></td>
|
96
|
-
|
96
|
+
<td class="text-end"><span data-bs-toggle="tooltip" title="{{ variant.reference }}">{{ variant.str_ref or "." }}</span></td>
|
97
97
|
<td>{{ str_status(variant) }}</td>
|
98
98
|
<td>{% for sample in variant.samples %}
|
99
99
|
{% if sample.genotype_call != "./." %}
|
@@ -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.
|
50
|
+
controllers.reset_all_dismissed(store, institute_obj, case_obj)
|
51
51
|
return redirect(request.referrer)
|
52
52
|
|
53
53
|
|
scout/server/config.py
CHANGED
@@ -75,10 +75,9 @@ ACCREDITATION_BADGE = "swedac-1926-iso17025.png"
|
|
75
75
|
# Connection details for Scout REViewer service
|
76
76
|
# SCOUT_REVIEWER_URL = "http://127.0.0.1:8000/reviewer"
|
77
77
|
|
78
|
-
#
|
79
|
-
#
|
80
|
-
#
|
81
|
-
# CLOUD_IGV_TRACKS = [
|
78
|
+
# Custom IGV tracks can be configured here to allow users to enable them on their IGV views.
|
79
|
+
# A number of publicly-available tracks can be found here: https://trackhubregistry.org/ or downloaded from
|
80
|
+
# CUSTOM_IGV_TRACKS = [
|
82
81
|
# {
|
83
82
|
# "name": "public_tracks",
|
84
83
|
# "access": "public",
|
@@ -99,6 +98,19 @@ ACCREDITATION_BADGE = "swedac-1926-iso17025.png"
|
|
99
98
|
# },
|
100
99
|
# ],
|
101
100
|
# },
|
101
|
+
# {
|
102
|
+
# "name": "local_tracks",
|
103
|
+
# "access": "public",
|
104
|
+
# "tracks": [
|
105
|
+
# {
|
106
|
+
# "name": "MANE",
|
107
|
+
# "type": "annotation",
|
108
|
+
# "format": "bigbed",
|
109
|
+
# "build": "38",
|
110
|
+
# "url": "scout/resources/custom_igv_tracks/mane.bb",
|
111
|
+
# },
|
112
|
+
# ],
|
113
|
+
# },
|
102
114
|
# ]
|
103
115
|
|
104
116
|
# Chanjo-Report
|
@@ -6,10 +6,11 @@ from flask_login import LoginManager
|
|
6
6
|
from flask_mail import Mail
|
7
7
|
|
8
8
|
from scout.adapter import MongoAdapter
|
9
|
-
from scout.utils.
|
9
|
+
from scout.utils.track_resources import AlignTrackHandler
|
10
10
|
|
11
11
|
from .beacon_extension import Beacon
|
12
12
|
from .bionano_extension import BioNanoAccessAPI
|
13
|
+
from .chanjo_extension import ChanjoReport
|
13
14
|
from .clinvar_extension import ClinVarApi
|
14
15
|
from .gens_extension import GensViewer
|
15
16
|
from .ldap_extension import LdapManager
|
@@ -33,5 +34,6 @@ phenopacketapi = PhenopacketAPI()
|
|
33
34
|
rerunner = RerunnerService()
|
34
35
|
matchmaker = MatchMaker()
|
35
36
|
beacon = Beacon()
|
36
|
-
|
37
|
+
config_igv_tracks = AlignTrackHandler()
|
37
38
|
bionano_access = BioNanoAccessAPI()
|
39
|
+
chanjo_report = ChanjoReport()
|
@@ -0,0 +1,58 @@
|
|
1
|
+
"""
|
2
|
+
Generate coverage reports using chanjo and chanjo-report. Documentation under -> `docs/admin-guide/chanjo_coverage_integration.md`
|
3
|
+
"""
|
4
|
+
|
5
|
+
import logging
|
6
|
+
|
7
|
+
from flask import current_app, request
|
8
|
+
from flask_babel import Babel
|
9
|
+
from markupsafe import Markup
|
10
|
+
|
11
|
+
LOG = logging.getLogger(__name__)
|
12
|
+
try:
|
13
|
+
from chanjo_report.server.app import configure_template_filters
|
14
|
+
from chanjo_report.server.blueprints import report_bp
|
15
|
+
from chanjo_report.server.extensions import api as chanjo_api
|
16
|
+
except ImportError as error:
|
17
|
+
chanjo_api = None
|
18
|
+
report_bp = None
|
19
|
+
configure_template_filters = None
|
20
|
+
LOG.warning("chanjo-report is not properly installed! %s.", error)
|
21
|
+
|
22
|
+
|
23
|
+
class ChanjoReport:
|
24
|
+
"""Interfaces with chanjo-report. Creates the /reports endpoints in scout domain. Use Babel to set report language."""
|
25
|
+
|
26
|
+
def init_app(self, app):
|
27
|
+
if not chanjo_api:
|
28
|
+
raise ImportError(
|
29
|
+
"An SQL db path was given, but chanjo-report could not be registered."
|
30
|
+
)
|
31
|
+
|
32
|
+
def get_locale():
|
33
|
+
"""Determine locale to use for translations."""
|
34
|
+
accept_languages = current_app.config.get("ACCEPT_LANGUAGES", ["en"])
|
35
|
+
|
36
|
+
# first check request args
|
37
|
+
session_language = Markup.escape(request.args.get("lang"))
|
38
|
+
if session_language in accept_languages:
|
39
|
+
current_app.logger.info("using session language: %s", session_language)
|
40
|
+
return session_language
|
41
|
+
|
42
|
+
# language can be forced in config
|
43
|
+
user_language = current_app.config.get("REPORT_LANGUAGE")
|
44
|
+
if user_language:
|
45
|
+
return user_language
|
46
|
+
|
47
|
+
# try to guess the language from the user accept header that
|
48
|
+
# the browser transmits. We support de/fr/en in this example.
|
49
|
+
# The best match wins.
|
50
|
+
return request.accept_languages.best_match(accept_languages)
|
51
|
+
|
52
|
+
babel = Babel(app)
|
53
|
+
babel.init_app(app, locale_selector=get_locale)
|
54
|
+
chanjo_api.init_app(app)
|
55
|
+
configure_template_filters(app)
|
56
|
+
app.register_blueprint(report_bp, url_prefix="/reports")
|
57
|
+
app.config["chanjo_report"] = True
|
58
|
+
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = True if app.debug else False
|
@@ -351,6 +351,24 @@ body {
|
|
351
351
|
}
|
352
352
|
/* end of sidebar-related stuff */
|
353
353
|
|
354
|
+
/* Closed submenu icon - general case */
|
355
|
+
.text-body[aria-expanded="false"] .collapse-icon::after {
|
356
|
+
content: " \f0d7";
|
357
|
+
font-family: "Font Awesome 5 Free", ui-monospace;
|
358
|
+
font-weight: 900;
|
359
|
+
display: inline;
|
360
|
+
text-align: left;
|
361
|
+
}
|
362
|
+
/* Opened submenu icon - general case */
|
363
|
+
.text-body[aria-expanded="true"] .collapse-icon::after {
|
364
|
+
content: " \f0da";
|
365
|
+
font-family: "Font Awesome 5 Free", ui-monospace;
|
366
|
+
font-weight: 900;
|
367
|
+
display: inline;
|
368
|
+
text-align: left;
|
369
|
+
}
|
370
|
+
/* end of sidebar-related stuff */
|
371
|
+
|
354
372
|
option {
|
355
373
|
width: 100%;
|
356
374
|
}
|
scout/server/utils.py
CHANGED
@@ -206,12 +206,26 @@ def user_institutes(store, login_user):
|
|
206
206
|
return institutes
|
207
207
|
|
208
208
|
|
209
|
+
def case_has_mtdna_report(case_obj: dict):
|
210
|
+
"""Display mtDNA report on the case sidebar only for some specific cases."""
|
211
|
+
if case_obj.get("track", "rare") == "cancer":
|
212
|
+
return
|
213
|
+
for ind in case_obj.get("individuals", []):
|
214
|
+
if ind.get("analysis_type") == "wts":
|
215
|
+
continue
|
216
|
+
case_obj["mtdna_report"] = True
|
217
|
+
return
|
218
|
+
|
219
|
+
|
209
220
|
def case_has_chanjo_coverage(case_obj: dict):
|
210
221
|
"""Set case_obj["chanjo_coverage"] to True if there is an instance of chanjo available and case has coverage stats in chanjo."""
|
211
222
|
|
212
|
-
chanjo_instance: bool = bool(current_app.config.get("
|
223
|
+
chanjo_instance: bool = bool(current_app.config.get("chanjo_report"))
|
213
224
|
if case_obj.get("track", "rare") != "cancer" and chanjo_instance:
|
214
|
-
case_obj
|
225
|
+
for ind in case_obj.get("individuals", []):
|
226
|
+
if ind.get("analysis_type") != "wts":
|
227
|
+
case_obj["chanjo_coverage"] = True
|
228
|
+
return
|
215
229
|
|
216
230
|
|
217
231
|
def case_has_chanjo2_coverage(case_obj: dict):
|
scout/utils/acmg.py
CHANGED
@@ -190,27 +190,40 @@ def get_acmg(acmg_terms):
|
|
190
190
|
bs_terms = []
|
191
191
|
# Collection of terms with supporting Benign evidence
|
192
192
|
bp_terms = []
|
193
|
+
|
194
|
+
suffix_map = {
|
195
|
+
"_Strong": {"P": ps_terms, "B": bs_terms},
|
196
|
+
"_Moderate": {"P": pm_terms},
|
197
|
+
"_Supporting": {"P": pp_terms, "B": bp_terms},
|
198
|
+
}
|
199
|
+
|
200
|
+
prefix_map = {
|
201
|
+
"PS": ps_terms,
|
202
|
+
"PM": pm_terms,
|
203
|
+
"PP": pp_terms,
|
204
|
+
"BS": bs_terms,
|
205
|
+
"BP": bp_terms,
|
206
|
+
}
|
207
|
+
|
193
208
|
for term in acmg_terms:
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
elif term.startswith("BP"):
|
213
|
-
bp_terms.append(term)
|
209
|
+
for suffix, prefix_dict in suffix_map.items():
|
210
|
+
if term.endswith(suffix):
|
211
|
+
for prefix, term_list in prefix_dict.items():
|
212
|
+
if term.startswith(prefix):
|
213
|
+
term_list.append(term)
|
214
|
+
break
|
215
|
+
break
|
216
|
+
else:
|
217
|
+
# Do we match any of the two standalone terms
|
218
|
+
if term.startswith("PVS"):
|
219
|
+
pvs = True
|
220
|
+
elif term.startswith("BA"):
|
221
|
+
ba = True
|
222
|
+
else: # Check remaining prefixes if no suffix match or standalone criteria match
|
223
|
+
for prefix, term_list in prefix_map.items():
|
224
|
+
if term.startswith(prefix):
|
225
|
+
term_list.append(term)
|
226
|
+
break
|
214
227
|
|
215
228
|
# We need to start by checking for Pathogenecity
|
216
229
|
pathogenic = is_pathogenic(pvs, ps_terms, pm_terms, pp_terms)
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# coding=UTF-8
|
2
|
+
import logging
|
3
|
+
import re
|
4
|
+
from os.path import abspath, exists, isabs
|
5
|
+
|
6
|
+
LOG = logging.getLogger(__name__)
|
7
|
+
|
8
|
+
REQUIRED_FIELDS = ["name", "type", "url"]
|
9
|
+
TRACK_KEYS = ["name", "type", "format", "url", "indexURL"]
|
10
|
+
URL_PATTERN = re.compile("https?://")
|
11
|
+
|
12
|
+
from typing import List
|
13
|
+
|
14
|
+
|
15
|
+
class AlignTrackHandler:
|
16
|
+
"""Class collecting external IGV tracks stored in the cloud"""
|
17
|
+
|
18
|
+
def init_app(self, app):
|
19
|
+
self.tracks = self.set_custom_tracks(
|
20
|
+
app.config.get("CUSTOM_IGV_TRACKS") or app.config.get("CLOUD_IGV_TRACKS")
|
21
|
+
)
|
22
|
+
|
23
|
+
def track_template(self, track_info: dict) -> dict:
|
24
|
+
"""Provides the template for a VCF track object"""
|
25
|
+
track = {}
|
26
|
+
|
27
|
+
for key in TRACK_KEYS:
|
28
|
+
if key in track_info:
|
29
|
+
track[key] = track_info[key]
|
30
|
+
|
31
|
+
# Save track only of it contains the minimal required keys
|
32
|
+
if all(track.get(key) for key in REQUIRED_FIELDS):
|
33
|
+
return track
|
34
|
+
else:
|
35
|
+
raise FileNotFoundError(
|
36
|
+
f"One or more custom tracks specified in the config file could not be used, Please provide all required keys:{REQUIRED_FIELDS}"
|
37
|
+
)
|
38
|
+
|
39
|
+
def set_local_track_path(self, path: str) -> str:
|
40
|
+
"""Returns the complete path to a local igv track file"""
|
41
|
+
if exists(path) and isabs(path):
|
42
|
+
return path
|
43
|
+
elif exists(path):
|
44
|
+
return abspath(path)
|
45
|
+
else:
|
46
|
+
raise FileNotFoundError(f"Could not verify path to IGV track:{path}")
|
47
|
+
|
48
|
+
def set_custom_tracks(self, track_dictionaries: List[dict]) -> dict:
|
49
|
+
"""Return a list of IGV tracks collected from the config settings
|
50
|
+
|
51
|
+
Args:
|
52
|
+
track_dictionaries: a list of cloud bucket dictionaries containing IGV tracks, can be public or private
|
53
|
+
"""
|
54
|
+
custom_tracks = {"37": [], "38": []}
|
55
|
+
# Loop over the bucket list and collect all public tracks
|
56
|
+
for track_category in track_dictionaries:
|
57
|
+
for track in track_category.get("tracks", []):
|
58
|
+
build = track.get("build")
|
59
|
+
if build not in ["37", "38"]:
|
60
|
+
LOG.warning(
|
61
|
+
"One or more IGV public tracks could not be used, Please provide a genome build ('37', '38') for it."
|
62
|
+
)
|
63
|
+
continue
|
64
|
+
track_obj = self.track_template(track)
|
65
|
+
|
66
|
+
if not URL_PATTERN.search(track_obj["url"]): # It's a local file
|
67
|
+
track_obj["url"] = self.set_local_track_path(track_obj["url"])
|
68
|
+
|
69
|
+
custom_tracks[build].append(track_obj)
|
70
|
+
return custom_tracks
|