scout-browser 4.102.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.
- scout/adapter/mongo/case.py +26 -122
- scout/adapter/mongo/clinvar.py +91 -25
- scout/adapter/mongo/event.py +0 -47
- scout/adapter/mongo/variant_loader.py +7 -3
- scout/build/variant/variant.py +1 -0
- scout/commands/load/variants.py +1 -1
- scout/commands/update/user.py +87 -49
- scout/constants/__init__.py +3 -0
- scout/constants/clinvar.py +10 -0
- scout/constants/variant_tags.py +18 -0
- scout/demo/NIST.trgt.stranger.vcf.gz +0 -0
- scout/demo/NIST.trgt.stranger.vcf.gz.tbi +0 -0
- scout/demo/__init__.py +1 -0
- scout/models/clinvar.py +86 -0
- scout/parse/variant/coordinates.py +5 -1
- scout/parse/variant/gene.py +5 -9
- scout/parse/variant/genotype.py +66 -42
- scout/parse/variant/variant.py +2 -0
- scout/server/app.py +71 -2
- scout/server/blueprints/alignviewers/templates/alignviewers/igv_viewer.html +2 -0
- scout/server/blueprints/cases/controllers.py +1 -1
- scout/server/blueprints/cases/templates/cases/case_report.html +19 -2
- scout/server/blueprints/cases/templates/cases/utils.html +8 -29
- scout/server/blueprints/clinvar/controllers.py +233 -53
- scout/server/blueprints/clinvar/form.py +38 -1
- scout/server/blueprints/clinvar/static/form_style.css +8 -1
- scout/server/blueprints/clinvar/templates/clinvar/clinvar_onc_submissions.html +200 -0
- scout/server/blueprints/clinvar/templates/clinvar/clinvar_submissions.html +3 -2
- scout/server/blueprints/clinvar/templates/clinvar/components.html +198 -0
- scout/server/blueprints/clinvar/templates/clinvar/multistep_add_onc_variant.html +187 -0
- scout/server/blueprints/clinvar/templates/clinvar/multistep_add_variant.html +9 -348
- scout/server/blueprints/clinvar/templates/clinvar/scripts.html +193 -0
- scout/server/blueprints/clinvar/views.py +90 -13
- scout/server/blueprints/institutes/controllers.py +44 -5
- scout/server/blueprints/institutes/forms.py +1 -0
- scout/server/blueprints/institutes/templates/overview/gene_variants.html +15 -6
- scout/server/blueprints/institutes/templates/overview/institute_sidebar.html +28 -2
- scout/server/blueprints/institutes/templates/overview/utils.html +1 -1
- scout/server/blueprints/institutes/views.py +17 -4
- scout/server/blueprints/mme/templates/mme/mme_submissions.html +2 -2
- scout/server/blueprints/omics_variants/templates/omics_variants/outliers.html +2 -2
- scout/server/blueprints/variant/controllers.py +1 -1
- scout/server/blueprints/variant/templates/variant/cancer-variant.html +2 -1
- scout/server/blueprints/variant/templates/variant/components.html +0 -1
- scout/server/blueprints/variant/templates/variant/sv-variant.html +2 -1
- scout/server/blueprints/variant/templates/variant/variant.html +2 -2
- scout/server/blueprints/variant/templates/variant/variant_details.html +32 -24
- scout/server/blueprints/variants/templates/variants/cancer-variants.html +5 -3
- scout/server/blueprints/variants/templates/variants/str-variants.html +4 -1
- scout/server/blueprints/variants/templates/variants/sv-variants.html +3 -3
- scout/server/blueprints/variants/templates/variants/utils.html +4 -0
- scout/server/blueprints/variants/templates/variants/variants.html +4 -4
- scout/server/extensions/clinvar_extension.py +2 -2
- scout/server/templates/layout.html +1 -1
- {scout_browser-4.102.0.dist-info → scout_browser-4.103.0.dist-info}/METADATA +1 -1
- {scout_browser-4.102.0.dist-info → scout_browser-4.103.0.dist-info}/RECORD +59 -53
- {scout_browser-4.102.0.dist-info → scout_browser-4.103.0.dist-info}/WHEEL +0 -0
- {scout_browser-4.102.0.dist-info → scout_browser-4.103.0.dist-info}/entry_points.txt +0 -0
- {scout_browser-4.102.0.dist-info → scout_browser-4.103.0.dist-info}/licenses/LICENSE +0 -0
scout/adapter/mongo/case.py
CHANGED
@@ -14,8 +14,6 @@ from werkzeug.datastructures import ImmutableMultiDict
|
|
14
14
|
|
15
15
|
from scout.build.case import build_case
|
16
16
|
from scout.constants import (
|
17
|
-
ACMG_MAP,
|
18
|
-
CCV_MAP,
|
19
17
|
ID_PROJECTION,
|
20
18
|
ORDERED_FILE_TYPE_MAP,
|
21
19
|
ORDERED_OMICS_FILE_TYPE_MAP,
|
@@ -1052,7 +1050,7 @@ class CaseHandler(object):
|
|
1052
1050
|
self.update_case_sanger_variants(institute_obj, case_obj, old_sanger_variants)
|
1053
1051
|
|
1054
1052
|
if keep_actions and old_evaluated_variants:
|
1055
|
-
self.update_variant_actions(
|
1053
|
+
self.update_variant_actions(case_obj, old_evaluated_variants)
|
1056
1054
|
|
1057
1055
|
return case_obj
|
1058
1056
|
|
@@ -1372,15 +1370,12 @@ class CaseHandler(object):
|
|
1372
1370
|
|
1373
1371
|
return case_verif_variants
|
1374
1372
|
|
1375
|
-
def update_variant_actions(
|
1373
|
+
def update_variant_actions(
|
1374
|
+
self, case_obj: dict, old_eval_variants: List[dict]
|
1375
|
+
) -> Dict[str, List[str]]:
|
1376
1376
|
"""Update existing variants of a case according to the tagged status
|
1377
1377
|
(manual_rank, dismiss_variant, mosaic_tags) of its previous variants
|
1378
1378
|
|
1379
|
-
Accepts:
|
1380
|
-
institute_obj(dict): an institute object
|
1381
|
-
case_obj(dict): a case object
|
1382
|
-
old_eval_variants(list(Variant))
|
1383
|
-
|
1384
1379
|
Returns:
|
1385
1380
|
updated_variants(dict): a dictionary like this:
|
1386
1381
|
'manual_rank' : [list of variant ids],
|
@@ -1391,131 +1386,43 @@ class CaseHandler(object):
|
|
1391
1386
|
'ccv_classification': [list of variant ids]
|
1392
1387
|
'is_commented': [list of variant ids]
|
1393
1388
|
"""
|
1394
|
-
|
1395
|
-
"manual_rank"
|
1396
|
-
"dismiss_variant"
|
1397
|
-
"mosaic_tags"
|
1398
|
-
"cancer_tier"
|
1399
|
-
"acmg_classification"
|
1400
|
-
"ccv_classification"
|
1401
|
-
"is_commented"
|
1402
|
-
|
1403
|
-
|
1404
|
-
update_action_map = {
|
1405
|
-
"manual_rank": self.update_manual_rank,
|
1406
|
-
"dismiss_variant": self.update_dismiss_variant,
|
1407
|
-
"mosaic_tags": self.update_mosaic_tags,
|
1408
|
-
"cancer_tier": self.update_cancer_tier,
|
1409
|
-
}
|
1410
|
-
|
1389
|
+
ACTION_KEYS = [
|
1390
|
+
"manual_rank",
|
1391
|
+
"dismiss_variant",
|
1392
|
+
"mosaic_tags",
|
1393
|
+
"cancer_tier",
|
1394
|
+
"acmg_classification",
|
1395
|
+
"ccv_classification",
|
1396
|
+
"is_commented",
|
1397
|
+
]
|
1398
|
+
updated_variants = {action: [] for action in ACTION_KEYS}
|
1411
1399
|
LOG.debug(
|
1412
1400
|
"Updating action status for {} variants in case:{}".format(
|
1413
1401
|
len(old_eval_variants), case_obj["_id"]
|
1414
1402
|
)
|
1415
1403
|
)
|
1416
|
-
|
1417
1404
|
n_status_updated = 0
|
1418
1405
|
for old_var in old_eval_variants:
|
1419
1406
|
# search for the same variant in newly uploaded vars for this case
|
1420
1407
|
display_name = old_var["display_name"]
|
1421
|
-
|
1422
1408
|
new_var = self.variant_collection.find_one(
|
1423
1409
|
{"case_id": case_obj["_id"], "display_name": display_name}
|
1424
1410
|
)
|
1425
|
-
|
1426
1411
|
if new_var is None: # same var is no more among case variants, skip it
|
1427
1412
|
LOG.warning(
|
1428
1413
|
"Trying to propagate manual action from an old variant to a new, but couldn't find same variant any more"
|
1429
1414
|
)
|
1430
1415
|
continue
|
1431
1416
|
|
1432
|
-
for action in
|
1433
|
-
|
1434
|
-
|
1435
|
-
|
1436
|
-
|
1437
|
-
old_var.get(action) is None and action != "is_commented"
|
1438
|
-
): # tag new variant accordingly
|
1439
|
-
continue
|
1440
|
-
verb = action
|
1441
|
-
if action == "acmg_classification":
|
1442
|
-
verb = "acmg"
|
1443
|
-
if action == "is_commented":
|
1444
|
-
verb = "comment"
|
1445
|
-
if action == "ccv_classification":
|
1446
|
-
verb = "ccv"
|
1447
|
-
|
1448
|
-
old_event = self.event_collection.find_one(
|
1449
|
-
{
|
1450
|
-
"case": case_obj["_id"],
|
1451
|
-
"verb": verb,
|
1452
|
-
"variant_id": old_var["variant_id"],
|
1453
|
-
"category": "variant",
|
1454
|
-
},
|
1455
|
-
sort=[("updated_at", pymongo.DESCENDING)],
|
1456
|
-
)
|
1457
|
-
|
1458
|
-
if old_event is None:
|
1459
|
-
continue
|
1460
|
-
|
1461
|
-
user_obj = self.user(old_event["user_id"])
|
1462
|
-
if user_obj is None:
|
1463
|
-
continue
|
1464
|
-
|
1465
|
-
updated_variant = None
|
1466
|
-
|
1467
|
-
if action == "is_commented":
|
1468
|
-
updated_comments = self.comments_reupload(
|
1469
|
-
old_var, new_var, institute_obj, case_obj
|
1470
|
-
)
|
1471
|
-
if updated_comments > 0:
|
1472
|
-
LOG.info(
|
1473
|
-
"Created {} new comments for variant {} after reupload".format(
|
1474
|
-
updated_comments, display_name
|
1475
|
-
)
|
1476
|
-
)
|
1477
|
-
updated_variant = new_var
|
1478
|
-
|
1479
|
-
# create a link to the new variant for the events
|
1480
|
-
link = "/{0}/{1}/{2}".format(
|
1481
|
-
new_var["institute"], case_obj["display_name"], new_var["_id"]
|
1482
|
-
)
|
1483
|
-
|
1484
|
-
if action == "acmg_classification":
|
1485
|
-
str_classif = ACMG_MAP.get(old_var.get("acmg_classification"))
|
1486
|
-
updated_variant = self.update_acmg(
|
1487
|
-
institute_obj=institute_obj,
|
1488
|
-
case_obj=case_obj,
|
1489
|
-
user_obj=user_obj,
|
1490
|
-
link=link,
|
1491
|
-
variant_obj=new_var,
|
1492
|
-
acmg_str=str_classif,
|
1417
|
+
for action in ACTION_KEYS:
|
1418
|
+
if old_var.get(action):
|
1419
|
+
updated_variant = self.variant_collection.find_one_and_update(
|
1420
|
+
{"_id": new_var["_id"]},
|
1421
|
+
{"$set": {action: old_var.get(action)}},
|
1493
1422
|
)
|
1494
|
-
|
1495
|
-
|
1496
|
-
|
1497
|
-
updated_variant = self.update_ccv(
|
1498
|
-
institute_obj=institute_obj,
|
1499
|
-
case_obj=case_obj,
|
1500
|
-
user_obj=user_obj,
|
1501
|
-
link=link,
|
1502
|
-
variant_obj=new_var,
|
1503
|
-
ccv_str=str_classif,
|
1504
|
-
)
|
1505
|
-
|
1506
|
-
if action in update_action_map.keys():
|
1507
|
-
updated_variant = update_action_map[action](
|
1508
|
-
institute_obj,
|
1509
|
-
case_obj,
|
1510
|
-
user_obj,
|
1511
|
-
link,
|
1512
|
-
new_var,
|
1513
|
-
old_var.get(action),
|
1514
|
-
)
|
1515
|
-
|
1516
|
-
if updated_variant is not None:
|
1517
|
-
n_status_updated += 1
|
1518
|
-
updated_variants[action].append(updated_variant["_id"])
|
1423
|
+
if updated_variant:
|
1424
|
+
n_status_updated += 1
|
1425
|
+
updated_variants[action].append(new_var["_id"])
|
1519
1426
|
|
1520
1427
|
LOG.info("Variant actions updated {} times".format(n_status_updated))
|
1521
1428
|
return updated_variants
|
@@ -1591,14 +1498,11 @@ class CaseHandler(object):
|
|
1591
1498
|
|
1592
1499
|
else:
|
1593
1500
|
# old variant had Sanger validation ordered
|
1594
|
-
# check old event to collect user_obj that ordered the verification:
|
1595
1501
|
# set sanger ordered status for the new variant as well:
|
1596
|
-
updated_var = self.
|
1597
|
-
|
1598
|
-
|
1599
|
-
|
1600
|
-
link=link,
|
1601
|
-
variant=new_var,
|
1502
|
+
updated_var = self.variant_collection.find_one_and_update(
|
1503
|
+
{"_id": new_var["_id"]},
|
1504
|
+
{"$set": {"sanger_ordered": True}},
|
1505
|
+
return_document=pymongo.ReturnDocument.AFTER,
|
1602
1506
|
)
|
1603
1507
|
if updated_var:
|
1604
1508
|
updated_variants["updated_ordered"].append(updated_var["_id"])
|
scout/adapter/mongo/clinvar.py
CHANGED
@@ -2,20 +2,22 @@
|
|
2
2
|
import logging
|
3
3
|
import re
|
4
4
|
from datetime import datetime
|
5
|
-
from typing import List, Optional
|
5
|
+
from typing import Dict, List, Optional
|
6
6
|
|
7
7
|
import pymongo
|
8
8
|
from bson.objectid import ObjectId
|
9
9
|
from pymongo import ReturnDocument
|
10
10
|
|
11
|
+
from scout.constants.clinvar import ASSERTION_CRITERIA_ONC_ID, ASSERTION_ONC_ONC_DB
|
12
|
+
|
11
13
|
LOG = logging.getLogger(__name__)
|
12
14
|
|
13
15
|
|
14
16
|
class ClinVarHandler(object):
|
15
17
|
"""Class to handle clinvar submissions for the mongo adapter"""
|
16
18
|
|
17
|
-
def
|
18
|
-
"""Create an open ClinVar submission for an institute."""
|
19
|
+
def create_germline_submission(self, institute_id: str, user_id: str) -> ObjectId:
|
20
|
+
"""Create an open ClinVar germline submission for an institute."""
|
19
21
|
|
20
22
|
submission_obj = {
|
21
23
|
"status": "open",
|
@@ -23,7 +25,24 @@ class ClinVarHandler(object):
|
|
23
25
|
"institute_id": institute_id,
|
24
26
|
"created_by": user_id,
|
25
27
|
}
|
26
|
-
LOG.info("Creating a new ClinVar submission for institute %s", institute_id)
|
28
|
+
LOG.info("Creating a new ClinVar germline submission for institute %s", institute_id)
|
29
|
+
result = self.clinvar_submission_collection.insert_one(submission_obj)
|
30
|
+
return result.inserted_id
|
31
|
+
|
32
|
+
def create_oncogenicity_submission(self, institute_id: str, user_id: str) -> ObjectId:
|
33
|
+
"""Create an open ClinVar oncogenicity submission for an institute."""
|
34
|
+
|
35
|
+
submission_obj = {
|
36
|
+
"status": "open",
|
37
|
+
"type": "oncogenicity",
|
38
|
+
"created_at": datetime.now(),
|
39
|
+
"updated_at": datetime.now(),
|
40
|
+
"institute_id": institute_id,
|
41
|
+
"created_by": user_id,
|
42
|
+
"assertionCriteria": {"db": ASSERTION_ONC_ONC_DB, "id": ASSERTION_CRITERIA_ONC_ID},
|
43
|
+
"oncogenicitySubmission": [],
|
44
|
+
}
|
45
|
+
LOG.info("Creating a new ClinVar oncogenicity submission for institute %s", institute_id)
|
27
46
|
result = self.clinvar_submission_collection.insert_one(submission_obj)
|
28
47
|
return result.inserted_id
|
29
48
|
|
@@ -65,18 +84,31 @@ class ClinVarHandler(object):
|
|
65
84
|
# return deleted_count, deleted_submissions
|
66
85
|
return deleted_objects, deleted_submissions
|
67
86
|
|
68
|
-
def
|
69
|
-
"""Retrieve the database id of an open ClinVar submission for an institute,
|
87
|
+
def get_open_germline_clinvar_submission(self, institute_id: str, user_id: str) -> dict:
|
88
|
+
"""Retrieve the database id of an open ClinVar germline submission for an institute,
|
70
89
|
if none is available then creates a new submission dictionary and returns it.
|
71
90
|
"""
|
72
91
|
|
73
|
-
LOG.info("Retrieving an open clinvar submission for institute %s", institute_id)
|
74
92
|
query = dict(institute_id=institute_id, status="open")
|
75
93
|
submission = self.clinvar_submission_collection.find_one(query)
|
76
94
|
|
77
95
|
# If there is no open submission for this institute, create one
|
78
96
|
if submission is None:
|
79
|
-
submission_id = self.
|
97
|
+
submission_id = self.create_germline_submission(institute_id, user_id)
|
98
|
+
submission = self.clinvar_submission_collection.find_one({"_id": submission_id})
|
99
|
+
|
100
|
+
return submission
|
101
|
+
|
102
|
+
def get_open_onc_clinvar_submission(self, institute_id: str, user_id: str) -> dict:
|
103
|
+
"""Retrieve the database id of an open ClinVar oncogenicity submission for an institute,
|
104
|
+
if none is available then creates a new submission dictionary and returns it.
|
105
|
+
"""
|
106
|
+
|
107
|
+
query = dict(institute_id=institute_id, status="open", type="oncogenicity")
|
108
|
+
submission = self.clinvar_submission_collection.find_one(query)
|
109
|
+
# If there is no open submission for this institute, create one
|
110
|
+
if submission is None:
|
111
|
+
submission_id = self.create_oncogenicity_submission(institute_id, user_id)
|
80
112
|
submission = self.clinvar_submission_collection.find_one({"_id": submission_id})
|
81
113
|
|
82
114
|
return submission
|
@@ -230,9 +262,14 @@ class ClinVarHandler(object):
|
|
230
262
|
"updated_at": result.get("updated_at"),
|
231
263
|
}
|
232
264
|
|
233
|
-
def
|
234
|
-
"""Collect all open and closed
|
235
|
-
query =
|
265
|
+
def get_clinvar_onc_submissions(self, institute_id: str) -> pymongo.cursor.Cursor:
|
266
|
+
"""Collect all open and closed ClinVar oncogenocity submissions for an institute."""
|
267
|
+
query = {"institute_id": institute_id, "type": "oncogenicity"}
|
268
|
+
return self.clinvar_submission_collection.find(query).sort("updated_at", pymongo.DESCENDING)
|
269
|
+
|
270
|
+
def get_clinvar_germline_submissions(self, institute_id: str) -> List[dict]:
|
271
|
+
"""Collect all open and closed ClinVar germline submissions for an institute."""
|
272
|
+
query = {"institute_id": institute_id, "type": {"$exists": False}}
|
236
273
|
results = list(
|
237
274
|
self.clinvar_submission_collection.find(query).sort("updated_at", pymongo.DESCENDING)
|
238
275
|
)
|
@@ -298,8 +335,8 @@ class ClinVarHandler(object):
|
|
298
335
|
criteria = {"assertionCriteriaDB": amc_db, "assertionCriteriaID": amc_id}
|
299
336
|
return criteria
|
300
337
|
|
301
|
-
def clinvar_objs(self, submission_id, key_id):
|
302
|
-
"""Collects a list of objects from the
|
338
|
+
def clinvar_objs(self, submission_id: str, key_id: str) -> list:
|
339
|
+
"""Collects a list of objects from the ClinVar collection (variants of case data) as specified by the key_id in the submission.
|
303
340
|
|
304
341
|
Args:
|
305
342
|
submission_id(str): the _id key of a clinvar submission
|
@@ -308,8 +345,8 @@ class ClinVarHandler(object):
|
|
308
345
|
|
309
346
|
Returns:
|
310
347
|
clinvar_objects(list) : a list of clinvar objects (either variants of casedata)
|
311
|
-
|
312
348
|
"""
|
349
|
+
|
313
350
|
# Get a submission object
|
314
351
|
submission = self.clinvar_submission_collection.find_one({"_id": ObjectId(submission_id)})
|
315
352
|
|
@@ -319,7 +356,7 @@ class ClinVarHandler(object):
|
|
319
356
|
clinvar_objects = self.clinvar_collection.find({"_id": {"$in": clinvar_obj_ids}})
|
320
357
|
return list(clinvar_objects)
|
321
358
|
|
322
|
-
return
|
359
|
+
return []
|
323
360
|
|
324
361
|
def rename_casedata_samples(self, submission_id, case_id, old_name, new_name):
|
325
362
|
"""Rename all samples associated to a clinVar submission
|
@@ -403,20 +440,22 @@ class ClinVarHandler(object):
|
|
403
440
|
return_document=ReturnDocument.AFTER,
|
404
441
|
)
|
405
442
|
|
406
|
-
def
|
407
|
-
"""
|
408
|
-
|
409
|
-
Args:
|
410
|
-
case_id(str): a case _id
|
443
|
+
def delete_clinvar_onc_var(self, submission: str, variant_id: str) -> dict:
|
444
|
+
"""Removes an oncogenicity submission or one or its variants."""
|
411
445
|
|
412
|
-
|
413
|
-
|
446
|
+
return self.clinvar_submission_collection.find_one_and_update(
|
447
|
+
{"_id": ObjectId(submission)},
|
448
|
+
{"$pull": {"oncogenicitySubmission": {"variant_id": variant_id}}},
|
449
|
+
return_document=pymongo.ReturnDocument.AFTER,
|
450
|
+
)
|
414
451
|
|
415
|
-
|
452
|
+
def case_to_clinvars(self, case_id: str) -> Dict[str, dict]:
|
453
|
+
"""Get all variants included in ClinVar submissions for a case. Returns a dictionary with variant IDs as keys and submissions as values."""
|
416
454
|
query = dict(case_id=case_id, csv_type="variant")
|
417
|
-
|
455
|
+
germline_clinvar_objs = list(self.clinvar_collection.find(query))
|
456
|
+
oncogenic_clinvar_objs = []
|
418
457
|
submitted_vars = {}
|
419
|
-
for clinvar in
|
458
|
+
for clinvar in germline_clinvar_objs + oncogenic_clinvar_objs:
|
420
459
|
submitted_vars[clinvar.get("local_id")] = clinvar
|
421
460
|
|
422
461
|
return submitted_vars
|
@@ -454,3 +493,30 @@ class ClinVarHandler(object):
|
|
454
493
|
for clinvar_var in clinvar_vars_for_case:
|
455
494
|
if variant_id in clinvar_var["link"]:
|
456
495
|
return clinvar_var["user_name"]
|
496
|
+
|
497
|
+
def get_onc_submission_json(self, submission: str) -> Optional[dict]:
|
498
|
+
"""Returns a json oncogenicity submission file, as a json."""
|
499
|
+
|
500
|
+
submission_dict = self.clinvar_submission_collection.find_one(
|
501
|
+
{"_id": ObjectId(submission), "type": "oncogenicity"}
|
502
|
+
)
|
503
|
+
if not submission_dict:
|
504
|
+
return
|
505
|
+
|
506
|
+
for key in [
|
507
|
+
"_id",
|
508
|
+
"clinvar_subm_id",
|
509
|
+
"status",
|
510
|
+
"type",
|
511
|
+
"created_at",
|
512
|
+
"updated_at",
|
513
|
+
"created_by",
|
514
|
+
"institute_id",
|
515
|
+
]:
|
516
|
+
submission_dict.pop(key, None)
|
517
|
+
|
518
|
+
for var in submission_dict.get("oncogenicitySubmission", []):
|
519
|
+
for key in ["institute_id", "case_id", "case_name", "variant_id"]:
|
520
|
+
var.pop(key, None)
|
521
|
+
|
522
|
+
return submission_dict
|
scout/adapter/mongo/event.py
CHANGED
@@ -552,50 +552,3 @@ class EventHandler(CaseEventHandler, VariantEventHandler):
|
|
552
552
|
)
|
553
553
|
self.event_collection.insert_one(event)
|
554
554
|
return updated_comment
|
555
|
-
|
556
|
-
def comments_reupload(self, old_var, new_var, institute_obj, case_obj):
|
557
|
-
"""Creates comments for a new variant after variant reupload
|
558
|
-
|
559
|
-
Accepts:
|
560
|
-
old_var(Variant): the deleted variant
|
561
|
-
new_var(Variant): the new variant replacing old_var
|
562
|
-
institute_obj(dict): an institute object
|
563
|
-
case_obj(dict): a case object
|
564
|
-
|
565
|
-
Returns:
|
566
|
-
new_comments(int): the number of created comments
|
567
|
-
"""
|
568
|
-
new_comments = 0
|
569
|
-
|
570
|
-
if new_var["_id"] == old_var["_id"]:
|
571
|
-
return new_comments
|
572
|
-
|
573
|
-
link = "/{0}/{1}/{2}".format(new_var["institute"], case_obj["display_name"], new_var["_id"])
|
574
|
-
|
575
|
-
# collect all comments for the old variant
|
576
|
-
comments_query = self.events(
|
577
|
-
variant_id=old_var["variant_id"],
|
578
|
-
comments=True,
|
579
|
-
institute=institute_obj,
|
580
|
-
case=case_obj,
|
581
|
-
)
|
582
|
-
|
583
|
-
# and create the same comment for the new variant
|
584
|
-
for old_comment in comments_query:
|
585
|
-
comment_user = self.user(old_comment["user_id"])
|
586
|
-
if comment_user is None:
|
587
|
-
continue
|
588
|
-
|
589
|
-
updated_comment = self.comment(
|
590
|
-
institute=institute_obj,
|
591
|
-
case=case_obj,
|
592
|
-
user=comment_user,
|
593
|
-
link=link,
|
594
|
-
variant=new_var,
|
595
|
-
content=old_comment.get("content"),
|
596
|
-
comment_level=old_comment.get("level"),
|
597
|
-
)
|
598
|
-
if updated_comment:
|
599
|
-
new_comments += 1
|
600
|
-
|
601
|
-
return new_comments
|
@@ -657,14 +657,18 @@ class VariantLoader(object):
|
|
657
657
|
if vcf_dict["category"] != category:
|
658
658
|
continue
|
659
659
|
|
660
|
-
LOG.info(f"Loading'{vcf_file_key}' variants")
|
661
660
|
variant_file = (
|
662
661
|
case_obj["vcf_files"].get(vcf_file_key) if case_obj.get("vcf_files") else None
|
663
662
|
)
|
664
663
|
|
665
|
-
if
|
664
|
+
if variant_file:
|
665
|
+
LOG.info(f"Loading {vcf_file_key} variants")
|
666
|
+
else:
|
667
|
+
continue
|
668
|
+
|
669
|
+
if not self._has_variants_in_file(variant_file):
|
666
670
|
LOG.warning(
|
667
|
-
f"File '{variant_file}' not found on disk. Please update case {case_obj['_id']} with a valid file path for variant category
|
671
|
+
f"File '{variant_file}' not found on disk. Please update case {case_obj['_id']} with a valid file path for the {category} variant category ."
|
668
672
|
)
|
669
673
|
continue
|
670
674
|
|
scout/build/variant/variant.py
CHANGED
@@ -190,6 +190,7 @@ def build_variant(
|
|
190
190
|
variant_obj["position"] = int(variant["position"])
|
191
191
|
variant_obj["quality"] = float(variant["quality"]) if variant["quality"] else None
|
192
192
|
variant_obj["rank_score"] = float(variant["rank_score"])
|
193
|
+
variant_obj["norm_rank_score"] = float(variant["norm_rank_score"])
|
193
194
|
variant_obj["simple_id"] = variant["ids"].get("simple_id")
|
194
195
|
variant_obj["sub_category"] = variant.get("sub_category")
|
195
196
|
|
scout/commands/load/variants.py
CHANGED
@@ -274,4 +274,4 @@ def variants(
|
|
274
274
|
adapter.update_case_sanger_variants(institute_obj, case_obj, old_sanger_variants)
|
275
275
|
|
276
276
|
if keep_actions and old_evaluated_variants:
|
277
|
-
adapter.update_variant_actions(
|
277
|
+
adapter.update_variant_actions(case_obj, old_evaluated_variants)
|
scout/commands/update/user.py
CHANGED
@@ -1,72 +1,110 @@
|
|
1
1
|
import logging
|
2
|
+
from typing import List, Optional, Tuple
|
2
3
|
|
3
4
|
import click
|
4
5
|
from flask.cli import with_appcontext
|
5
6
|
|
7
|
+
from scout.adapter import MongoAdapter
|
6
8
|
from scout.server.extensions import store
|
7
9
|
|
8
10
|
LOG = logging.getLogger(__name__)
|
11
|
+
USER_ROLES = ["admin", "mme_submitter", "beacon_submitter"]
|
9
12
|
|
10
13
|
|
11
14
|
@click.command("user", short_help="Update a user")
|
12
|
-
@click.option("--user-id", "-u", help="
|
13
|
-
@click.option(
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
help="Add a role to the user",
|
19
|
-
)
|
20
|
-
@click.option("--remove-admin", is_flag=True, help="Remove admin rights from user")
|
21
|
-
@click.option("--add-institute", "-i", multiple=True, help="Specify the institutes to add")
|
22
|
-
@click.option("--remove-institute", multiple=True, help="Specify the institutes to remove")
|
15
|
+
@click.option("--user-id", "-u", required=True, help="An email address that identifies the user")
|
16
|
+
@click.option("--update-role", "-r", type=click.Choice(USER_ROLES), help="Add a role to the user")
|
17
|
+
@click.option("--remove-admin", is_flag=True, help="(Deprecated) Remove admin role from the user")
|
18
|
+
@click.option("--remove-role", multiple=True, help="Specify roles to remove from the user")
|
19
|
+
@click.option("--add-institute", "-i", multiple=True, help="Specify institutes to add")
|
20
|
+
@click.option("--remove-institute", multiple=True, help="Specify institutes to remove")
|
23
21
|
@with_appcontext
|
24
|
-
def user(
|
25
|
-
|
26
|
-
|
27
|
-
|
22
|
+
def user(
|
23
|
+
user_id: str,
|
24
|
+
update_role: Optional[str],
|
25
|
+
remove_admin: bool,
|
26
|
+
remove_role: Tuple[str, ...],
|
27
|
+
add_institute: Tuple[str, ...],
|
28
|
+
remove_institute: Tuple[str, ...],
|
29
|
+
) -> None:
|
30
|
+
"""Update roles and institutes for a user in the database."""
|
28
31
|
adapter = store
|
29
|
-
|
30
32
|
user_obj = adapter.user(user_id)
|
31
|
-
|
32
33
|
if not user_obj:
|
33
34
|
LOG.warning("User %s could not be found", user_id)
|
34
35
|
raise click.Abort()
|
35
36
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
37
|
+
user_obj["roles"] = process_roles(
|
38
|
+
current_roles=user_obj.get("roles", []),
|
39
|
+
add_role=update_role,
|
40
|
+
remove_roles=remove_role,
|
41
|
+
remove_admin=remove_admin,
|
42
|
+
user_id=user_id,
|
43
|
+
)
|
44
|
+
user_obj["institutes"] = process_institutes(
|
45
|
+
current_institutes=user_obj.get("institutes", []),
|
46
|
+
add_institutes=add_institute,
|
47
|
+
remove_institutes=remove_institute,
|
48
|
+
adapter=adapter,
|
49
|
+
user_id=user_id,
|
50
|
+
)
|
51
|
+
adapter.update_user(user_obj)
|
52
|
+
|
53
|
+
|
54
|
+
def process_roles(
|
55
|
+
current_roles: List[str],
|
56
|
+
add_role: Optional[str],
|
57
|
+
remove_roles: Tuple[str, ...],
|
58
|
+
remove_admin: bool,
|
59
|
+
user_id: str,
|
60
|
+
) -> List[str]:
|
61
|
+
"""Define the list of roles for a user in the database."""
|
62
|
+
roles = set(current_roles)
|
63
|
+
|
64
|
+
if add_role:
|
65
|
+
if add_role in roles:
|
66
|
+
LOG.warning("User already has role '%s'", add_role)
|
42
67
|
else:
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
for institute_id in add_institute:
|
56
|
-
institute_obj = adapter.institute(institute_id)
|
57
|
-
if not institute_obj:
|
58
|
-
LOG.warning("Institute %s could not be found", institute_id)
|
68
|
+
roles.add(add_role)
|
69
|
+
LOG.info("Adding role '%s' to user '%s'", add_role, user_id)
|
70
|
+
|
71
|
+
all_remove = set(remove_roles)
|
72
|
+
if remove_admin and "admin" not in all_remove:
|
73
|
+
click.echo("⚠️ --remove-admin is deprecated. Use --remove-role admin instead.")
|
74
|
+
all_remove.add("admin")
|
75
|
+
|
76
|
+
for role in all_remove:
|
77
|
+
if role in roles:
|
78
|
+
roles.remove(role)
|
79
|
+
LOG.info("Removing role '%s' from user '%s'", role, user_id)
|
59
80
|
else:
|
60
|
-
|
61
|
-
LOG.info("Adding institute %s to user", institute_id)
|
81
|
+
LOG.info("User does not have role '%s'", role)
|
62
82
|
|
63
|
-
|
64
|
-
try:
|
65
|
-
existing_institutes.remove(institute_id)
|
66
|
-
LOG.info("Removing institute %s from user", institute_id)
|
67
|
-
except KeyError as err:
|
68
|
-
LOG.info("User does not have access to institute %s", institute_id)
|
83
|
+
return list(roles)
|
69
84
|
|
70
|
-
user_obj["institutes"] = list(existing_institutes)
|
71
85
|
|
72
|
-
|
86
|
+
def process_institutes(
|
87
|
+
current_institutes: List[str],
|
88
|
+
add_institutes: Tuple[str, ...],
|
89
|
+
remove_institutes: Tuple[str, ...],
|
90
|
+
adapter: MongoAdapter,
|
91
|
+
user_id: str,
|
92
|
+
) -> List[str]:
|
93
|
+
"""Define the list of institutes for a user in the database."""
|
94
|
+
institutes = set(current_institutes)
|
95
|
+
|
96
|
+
for inst in add_institutes:
|
97
|
+
if adapter.institute(inst):
|
98
|
+
institutes.add(inst)
|
99
|
+
LOG.info("Adding institute '%s' to user '%s'", inst, user_id)
|
100
|
+
else:
|
101
|
+
LOG.warning("Institute '%s' could not be found", inst)
|
102
|
+
|
103
|
+
for inst in remove_institutes:
|
104
|
+
if inst in institutes:
|
105
|
+
institutes.remove(inst)
|
106
|
+
LOG.info("Removing institute '%s' from user '%s'", inst, user_id)
|
107
|
+
else:
|
108
|
+
LOG.info("User does not have access to institute '%s'", inst)
|
109
|
+
|
110
|
+
return list(institutes)
|