grz-cli 0.3.0__tar.gz → 0.4.0__tar.gz
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.
- grz_cli-0.4.0/MANIFEST.in +2 -0
- {grz_cli-0.3.0/src/grz_cli.egg-info → grz_cli-0.4.0}/PKG-INFO +6 -3
- {grz_cli-0.3.0 → grz_cli-0.4.0}/README.md +2 -0
- {grz_cli-0.3.0 → grz_cli-0.4.0}/pyproject.toml +3 -2
- grz_cli-0.4.0/src/grz_cli/__init__.py +1 -0
- grz_cli-0.4.0/src/grz_cli/bam_validation.py +39 -0
- {grz_cli-0.3.0 → grz_cli-0.4.0}/src/grz_cli/cli.py +5 -4
- {grz_cli-0.3.0 → grz_cli-0.4.0}/src/grz_cli/constants.py +0 -1
- {grz_cli-0.3.0 → grz_cli-0.4.0}/src/grz_cli/download.py +5 -2
- {grz_cli-0.3.0 → grz_cli-0.4.0}/src/grz_cli/fastq_validation.py +41 -24
- {grz_cli-0.3.0 → grz_cli-0.4.0}/src/grz_cli/models/config.py +18 -0
- {grz_cli-0.3.0 → grz_cli-0.4.0}/src/grz_cli/parser.py +61 -11
- {grz_cli-0.3.0 → grz_cli-0.4.0}/src/grz_cli/progress_logging.py +3 -2
- {grz_cli-0.3.0 → grz_cli-0.4.0}/src/grz_cli/upload.py +8 -3
- {grz_cli-0.3.0 → grz_cli-0.4.0/src/grz_cli.egg-info}/PKG-INFO +6 -3
- {grz_cli-0.3.0 → grz_cli-0.4.0}/src/grz_cli.egg-info/SOURCES.txt +2 -1
- {grz_cli-0.3.0 → grz_cli-0.4.0}/src/grz_cli.egg-info/requires.txt +3 -2
- grz_cli-0.4.0/tests/test_bam_validation.py +15 -0
- {grz_cli-0.3.0 → grz_cli-0.4.0}/tests/test_cli.py +5 -3
- {grz_cli-0.3.0 → grz_cli-0.4.0}/tests/test_fastq_validation.py +1 -1
- {grz_cli-0.3.0 → grz_cli-0.4.0}/tests/test_file_progress_logging.py +1 -1
- {grz_cli-0.3.0 → grz_cli-0.4.0}/tests/test_upload.py +12 -11
- grz_cli-0.3.0/MANIFEST.in +0 -3
- grz_cli-0.3.0/src/grz_cli/__init__.py +0 -1
- grz_cli-0.3.0/src/grz_cli/resources/thresholds.json +0 -299
- {grz_cli-0.3.0 → grz_cli-0.4.0}/LICENSE +0 -0
- {grz_cli-0.3.0 → grz_cli-0.4.0}/setup.cfg +0 -0
- {grz_cli-0.3.0 → grz_cli-0.4.0}/src/__init__.py +0 -0
- {grz_cli-0.3.0 → grz_cli-0.4.0}/src/grz_cli/file_operations.py +0 -0
- {grz_cli-0.3.0 → grz_cli-0.4.0}/src/grz_cli/logging_setup.py +0 -0
- {grz_cli-0.3.0 → grz_cli-0.4.0}/src/grz_cli/models/__init__.py +0 -0
- {grz_cli-0.3.0 → grz_cli-0.4.0}/src/grz_cli/states.py +0 -0
- {grz_cli-0.3.0 → grz_cli-0.4.0}/src/grz_cli.egg-info/dependency_links.txt +0 -0
- {grz_cli-0.3.0 → grz_cli-0.4.0}/src/grz_cli.egg-info/entry_points.txt +0 -0
- {grz_cli-0.3.0 → grz_cli-0.4.0}/src/grz_cli.egg-info/top_level.txt +0 -0
- {grz_cli-0.3.0 → grz_cli-0.4.0}/tests/test_download.py +0 -0
- {grz_cli-0.3.0 → grz_cli-0.4.0}/tests/test_file_operations.py +0 -0
- {grz_cli-0.3.0 → grz_cli-0.4.0}/tests/test_parser.py +0 -0
- {grz_cli-0.3.0 → grz_cli-0.4.0}/tests/test_read_multiple_json.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: grz-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Tool for validation, encryption and upload of MV submissions to GDCs.
|
|
5
5
|
Author-email: Koray Kirli <koraykirli@gmail.com>, Mathias Lesche <mathias.lesche@tu-dresden.de>, "Florian R. Hölzlwimmer" <git.ich@frhoelzlwimmer.de>, Till Hartmann <till.hartmann@bih-charite.de>, Thomas Sell <thomas.sell@bih-charite.de>
|
|
6
6
|
License: MIT
|
|
@@ -11,7 +11,7 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
11
11
|
Classifier: Operating System :: OS Independent
|
|
12
12
|
Requires-Python: >=3.12
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
|
-
Requires-Dist: boto3<2,>=1.
|
|
14
|
+
Requires-Dist: boto3<2,>=1.36
|
|
15
15
|
Requires-Dist: click<9,>=8.1.7
|
|
16
16
|
Requires-Dist: crypt4gh<2,>=1.7
|
|
17
17
|
Requires-Dist: jsonschema<5,>=4.23.0
|
|
@@ -19,7 +19,8 @@ Requires-Dist: pyyaml<7,>=6.0.2
|
|
|
19
19
|
Requires-Dist: tqdm<5,>=4.66.5
|
|
20
20
|
Requires-Dist: pydantic<2.10,>=2.9.2
|
|
21
21
|
Requires-Dist: platformdirs<5,>=4.3.6
|
|
22
|
-
Requires-Dist: grz-pydantic-models>=1.2.
|
|
22
|
+
Requires-Dist: grz-pydantic-models>=1.2.1
|
|
23
|
+
Requires-Dist: pysam==0.23.*
|
|
23
24
|
Provides-Extra: test
|
|
24
25
|
Requires-Dist: pytest<9,>=8.3.3; extra == "test"
|
|
25
26
|
Requires-Dist: pytest-cov<6,>=5.0.0; extra == "test"
|
|
@@ -254,6 +255,8 @@ Option is for the usage at a GDC/GRZ.
|
|
|
254
255
|
|
|
255
256
|
## Testing
|
|
256
257
|
|
|
258
|
+
Please note that binary files used for testing are managed with [Git LFS](https://git-lfs.com), which will be needed to clone them locally with the git repository.
|
|
259
|
+
|
|
257
260
|
To run the tests, navigate to the root directory of your project and invoke `pytest`.
|
|
258
261
|
Alternatively, install `uv` and `tox` and run `uv run tox`.
|
|
259
262
|
|
|
@@ -223,6 +223,8 @@ Option is for the usage at a GDC/GRZ.
|
|
|
223
223
|
|
|
224
224
|
## Testing
|
|
225
225
|
|
|
226
|
+
Please note that binary files used for testing are managed with [Git LFS](https://git-lfs.com), which will be needed to clone them locally with the git repository.
|
|
227
|
+
|
|
226
228
|
To run the tests, navigate to the root directory of your project and invoke `pytest`.
|
|
227
229
|
Alternatively, install `uv` and `tox` and run `uv run tox`.
|
|
228
230
|
|
|
@@ -16,7 +16,7 @@ authors = [
|
|
|
16
16
|
]
|
|
17
17
|
dynamic = ["version"]
|
|
18
18
|
dependencies = [
|
|
19
|
-
"boto3 >=1.
|
|
19
|
+
"boto3 >=1.36,<2",
|
|
20
20
|
"click >=8.1.7,<9",
|
|
21
21
|
"crypt4gh >=1.7,<2",
|
|
22
22
|
"jsonschema >=4.23.0,<5",
|
|
@@ -24,7 +24,8 @@ dependencies = [
|
|
|
24
24
|
"tqdm >=4.66.5,<5",
|
|
25
25
|
"pydantic >=2.9.2,<2.10",
|
|
26
26
|
"platformdirs >=4.3.6,<5",
|
|
27
|
-
"grz-pydantic-models >=1.2.
|
|
27
|
+
"grz-pydantic-models >=1.2.1",
|
|
28
|
+
"pysam ==0.23.*"
|
|
28
29
|
]
|
|
29
30
|
classifiers = [
|
|
30
31
|
"Programming Language :: Python :: 3",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.0"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Validation methods for BAM files
|
|
3
|
+
|
|
4
|
+
e.g. warn on headers present, which can contain personally identifying
|
|
5
|
+
information
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from collections.abc import Generator
|
|
10
|
+
from os import PathLike
|
|
11
|
+
|
|
12
|
+
import pysam
|
|
13
|
+
|
|
14
|
+
log = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def validate_bam(bam_path: str | PathLike) -> Generator[str]:
|
|
18
|
+
"""Check if a BAM file is valid for submission"""
|
|
19
|
+
errors: list[str] = []
|
|
20
|
+
|
|
21
|
+
# disable pysam's SQ header presence enforcement (check_sq)
|
|
22
|
+
# these are used to optionally sort alignments by a custom contig order,
|
|
23
|
+
# which is unnecessary in unmapped BAM files
|
|
24
|
+
bam_file = pysam.AlignmentFile(str(bam_path), mode="rb", check_sq=False)
|
|
25
|
+
|
|
26
|
+
header = bam_file.header.to_dict()
|
|
27
|
+
# ignore the HD key as it is almost always present and extremely
|
|
28
|
+
# unlikely to accidentally contain identifying information
|
|
29
|
+
concerning_keys = header.keys() - {"HD"}
|
|
30
|
+
if concerning_keys:
|
|
31
|
+
log.warning(f"Detected a header in BAM file '{bam_path}', ensure it contains no private information:")
|
|
32
|
+
for key in concerning_keys:
|
|
33
|
+
for line in header[key]:
|
|
34
|
+
log.warning(f"@{key}")
|
|
35
|
+
for tag, value in line.items():
|
|
36
|
+
log.warning(f" {tag}: {value}")
|
|
37
|
+
log.warning("--- End of potentially-concerning BAM header information ---")
|
|
38
|
+
|
|
39
|
+
yield from errors
|
|
@@ -11,7 +11,7 @@ from pathlib import Path
|
|
|
11
11
|
from tempfile import NamedTemporaryFile
|
|
12
12
|
|
|
13
13
|
import click
|
|
14
|
-
import grz_pydantic_models
|
|
14
|
+
import grz_pydantic_models.submission.metadata
|
|
15
15
|
import platformdirs
|
|
16
16
|
import yaml
|
|
17
17
|
|
|
@@ -82,7 +82,7 @@ submission_id = click.option(
|
|
|
82
82
|
required=True,
|
|
83
83
|
type=str,
|
|
84
84
|
metavar="STRING",
|
|
85
|
-
help="S3 submission
|
|
85
|
+
help="S3 submission ID",
|
|
86
86
|
)
|
|
87
87
|
|
|
88
88
|
output_dir = click.option(
|
|
@@ -112,7 +112,7 @@ class OrderedGroup(click.Group):
|
|
|
112
112
|
@click.version_option(
|
|
113
113
|
version=importlib.metadata.version("grz-cli"),
|
|
114
114
|
prog_name="grz-cli",
|
|
115
|
-
message=f"%(prog)s v%(version)s (metadata schema versions: {', '.join(grz_pydantic_models.get_supported_versions())})",
|
|
115
|
+
message=f"%(prog)s v%(version)s (metadata schema versions: {', '.join(grz_pydantic_models.submission.metadata.get_supported_versions())})",
|
|
116
116
|
)
|
|
117
117
|
@click.option("--log-file", metavar="FILE", type=str, help="Path to log file")
|
|
118
118
|
@click.option(
|
|
@@ -275,7 +275,8 @@ def upload(
|
|
|
275
275
|
encrypted_files_dir=submission_dir / "encrypted_files",
|
|
276
276
|
threads=threads,
|
|
277
277
|
)
|
|
278
|
-
|
|
278
|
+
# output the generated submission ID
|
|
279
|
+
print(worker_inst.upload(config))
|
|
279
280
|
|
|
280
281
|
log.info("Upload finished!")
|
|
281
282
|
|
|
@@ -73,8 +73,11 @@ class S3BotoDownloadWorker:
|
|
|
73
73
|
return string
|
|
74
74
|
|
|
75
75
|
# configure proxies if proxy_url is defined
|
|
76
|
-
proxy_url =
|
|
77
|
-
config = Boto3Config(
|
|
76
|
+
proxy_url = self._config.s3_options.proxy_url
|
|
77
|
+
config = Boto3Config(
|
|
78
|
+
proxies={"http": str(proxy_url), "https": str(proxy_url)} if proxy_url is not None else None,
|
|
79
|
+
request_checksum_calculation=self._config.s3_options.request_checksum_calculation,
|
|
80
|
+
)
|
|
78
81
|
|
|
79
82
|
# Initialize S3 client for uploading
|
|
80
83
|
self._s3_client: boto3.session.Session.client = boto3_client(
|
|
@@ -64,40 +64,58 @@ def open_fastq(file_path: str | PathLike, progress=True) -> Generator[TextIO, No
|
|
|
64
64
|
yield typing.cast(TextIO, handle)
|
|
65
65
|
|
|
66
66
|
|
|
67
|
-
def calculate_fastq_stats(file_path) -> tuple[int,
|
|
67
|
+
def calculate_fastq_stats(file_path, expected_read_length: int | None = None) -> tuple[int, int | None]:
|
|
68
68
|
"""
|
|
69
69
|
Calculate line number and read lengths in FASTQ file.
|
|
70
70
|
:param file_path: Path to the FASTQ file
|
|
71
|
+
:param expected_read_length: Expected read length (None if not known)
|
|
71
72
|
:return: tuple with the following values:
|
|
72
|
-
-
|
|
73
|
-
-
|
|
73
|
+
- Number of lines in the file
|
|
74
|
+
- Observed read length
|
|
74
75
|
"""
|
|
75
|
-
read_lengths = set()
|
|
76
|
-
|
|
77
76
|
with open_fastq(file_path) as f:
|
|
78
77
|
for line_number, line in enumerate(f):
|
|
79
78
|
if (line_number % 4) == 1:
|
|
80
79
|
# Sequence lines are every 4th line starting from the 2nd
|
|
81
|
-
|
|
80
|
+
# Check if the read length is consistent
|
|
81
|
+
read_length = len(line.strip())
|
|
82
|
+
if expected_read_length is None:
|
|
83
|
+
expected_read_length = read_length
|
|
84
|
+
elif read_length != expected_read_length:
|
|
85
|
+
raise ValueError(
|
|
86
|
+
f"Read length mismatch at line {line_number + 1}: "
|
|
87
|
+
f"expected {expected_read_length}, found {read_length}"
|
|
88
|
+
)
|
|
82
89
|
|
|
83
90
|
return (
|
|
84
91
|
line_number + 1, # enumerate starts indexing at 0
|
|
85
|
-
|
|
92
|
+
expected_read_length,
|
|
86
93
|
)
|
|
87
94
|
|
|
88
95
|
|
|
89
|
-
def validate_fastq_file(
|
|
96
|
+
def validate_fastq_file(
|
|
97
|
+
fastq_file: str | PathLike, expected_read_length: int | None = None
|
|
98
|
+
) -> tuple[int, int, list[str]] | tuple[int, int | None, list[str]]:
|
|
90
99
|
"""
|
|
91
100
|
Validates a FASTQ file.
|
|
92
101
|
|
|
93
102
|
:param fastq_file: Path to the fastq file
|
|
103
|
+
:param expected_read_length: Expected read length (None if not known)
|
|
94
104
|
:return: Tuple with the following fields:
|
|
95
105
|
- number of lines
|
|
96
106
|
- set of observed read lengths
|
|
97
107
|
- list of errors found
|
|
98
108
|
"""
|
|
99
|
-
num_lines, read_lengths = calculate_fastq_stats(fastq_file)
|
|
100
109
|
errors = []
|
|
110
|
+
try:
|
|
111
|
+
if expected_read_length is not None:
|
|
112
|
+
log.debug("%s: expecting read length of %s", fastq_file, expected_read_length)
|
|
113
|
+
|
|
114
|
+
# Calculate the number of lines and read lengths
|
|
115
|
+
num_lines, read_length = calculate_fastq_stats(fastq_file, expected_read_length=expected_read_length)
|
|
116
|
+
except ValueError as e:
|
|
117
|
+
errors.append(f"{fastq_file}: {e}")
|
|
118
|
+
return -1, -1, errors
|
|
101
119
|
|
|
102
120
|
# Check if the number of lines in a FASTQ file is a multiple of 4.
|
|
103
121
|
if num_lines % 4 != 0:
|
|
@@ -105,40 +123,39 @@ def validate_fastq_file(fastq_file: str | PathLike) -> tuple[int, set[int], list
|
|
|
105
123
|
else:
|
|
106
124
|
log.debug("%s: %s lines", fastq_file, num_lines)
|
|
107
125
|
|
|
108
|
-
|
|
109
|
-
if len(read_lengths) != 1:
|
|
110
|
-
errors.append(
|
|
111
|
-
f"{fastq_file}: Inconsistent read lengths! "
|
|
112
|
-
f"Found reads with following lengths: {', '.join([str(c) for c in read_lengths])}"
|
|
113
|
-
)
|
|
114
|
-
else:
|
|
115
|
-
log.debug("%s: read length: %s", fastq_file, next(iter(read_lengths)))
|
|
116
|
-
|
|
117
|
-
return num_lines, read_lengths, errors
|
|
126
|
+
return num_lines, read_length, errors
|
|
118
127
|
|
|
119
128
|
|
|
120
|
-
def validate_single_end_reads(fastq_file: str | PathLike) -> Generator[str]:
|
|
129
|
+
def validate_single_end_reads(fastq_file: str | PathLike, expected_read_length: int | None = None) -> Generator[str]:
|
|
121
130
|
"""
|
|
122
131
|
Validate a single-end FASTQ file.
|
|
123
132
|
|
|
124
133
|
:param fastq_file: Path to the FASTQ file
|
|
134
|
+
:param expected_read_length: Expected read length (None if not known)
|
|
125
135
|
:return: Generator of errors, if any.
|
|
126
136
|
"""
|
|
127
|
-
num_lines, read_lengths, errors = validate_fastq_file(fastq_file)
|
|
137
|
+
num_lines, read_lengths, errors = validate_fastq_file(fastq_file, expected_read_length=expected_read_length)
|
|
128
138
|
yield from errors
|
|
129
139
|
|
|
130
140
|
|
|
131
|
-
def validate_paired_end_reads(
|
|
141
|
+
def validate_paired_end_reads(
|
|
142
|
+
fastq_file1: str | PathLike, fastq_file2: str | PathLike, expected_read_length: int | None = None
|
|
143
|
+
) -> Generator[str]:
|
|
132
144
|
"""
|
|
133
145
|
Validate two paired-end FASTQ files.
|
|
134
146
|
|
|
135
147
|
:param fastq_file1: Path to the first FASTQ file (Read 1)
|
|
136
148
|
:param fastq_file2: Path to the second FASTQ file (Read 2)
|
|
149
|
+
:param expected_read_length: Expected read length (None if not known)
|
|
137
150
|
:return: Generator of errors, if any.
|
|
138
151
|
"""
|
|
139
|
-
num_lines_file1, read_lengths_file1, errors_file1 = validate_fastq_file(
|
|
152
|
+
num_lines_file1, read_lengths_file1, errors_file1 = validate_fastq_file(
|
|
153
|
+
fastq_file1, expected_read_length=expected_read_length
|
|
154
|
+
)
|
|
140
155
|
yield from errors_file1
|
|
141
|
-
num_lines_file2, read_lengths_file2, errors_file2 = validate_fastq_file(
|
|
156
|
+
num_lines_file2, read_lengths_file2, errors_file2 = validate_fastq_file(
|
|
157
|
+
fastq_file2, expected_read_length=expected_read_length
|
|
158
|
+
)
|
|
142
159
|
yield from errors_file2
|
|
143
160
|
|
|
144
161
|
if num_lines_file1 != num_lines_file2:
|
|
@@ -67,6 +67,24 @@ class S3Options(StrictBaseModel):
|
|
|
67
67
|
The proxy URL for S3 operations (optional).
|
|
68
68
|
"""
|
|
69
69
|
|
|
70
|
+
request_checksum_calculation: str | None = None
|
|
71
|
+
"""
|
|
72
|
+
Whether to calculate checksums for S3 request payloads (optional).
|
|
73
|
+
Valid values are:
|
|
74
|
+
|
|
75
|
+
* ``when_supported`` -- When set, a checksum will be calculated for
|
|
76
|
+
all request payloads of operations modeled with the ``httpChecksum``
|
|
77
|
+
trait where ``requestChecksumRequired`` is ``true`` or a
|
|
78
|
+
``requestAlgorithmMember`` is modeled.
|
|
79
|
+
|
|
80
|
+
* ``when_required`` -- When set, a checksum will only be calculated
|
|
81
|
+
for request payloads of operations modeled with the ``httpChecksum``
|
|
82
|
+
trait where ``requestChecksumRequired`` is ``true`` or where a
|
|
83
|
+
``requestAlgorithmMember`` is modeled and supplied.
|
|
84
|
+
|
|
85
|
+
Defaults to None.
|
|
86
|
+
"""
|
|
87
|
+
|
|
70
88
|
|
|
71
89
|
FilePath = Annotated[Path, AfterValidator(lambda v: v.expanduser()), PathType("file")]
|
|
72
90
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import hashlib
|
|
5
6
|
import json
|
|
6
7
|
import logging
|
|
7
8
|
from collections.abc import Generator
|
|
@@ -9,7 +10,7 @@ from itertools import groupby
|
|
|
9
10
|
from os import PathLike
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
|
|
12
|
-
from grz_pydantic_models.
|
|
13
|
+
from grz_pydantic_models.submission.metadata.v1 import (
|
|
13
14
|
ChecksumType,
|
|
14
15
|
File,
|
|
15
16
|
FileType,
|
|
@@ -18,9 +19,10 @@ from grz_pydantic_models.v1_1_1.metadata import (
|
|
|
18
19
|
SequenceData,
|
|
19
20
|
SequencingLayout,
|
|
20
21
|
)
|
|
21
|
-
from grz_pydantic_models.
|
|
22
|
+
from grz_pydantic_models.submission.metadata.v1 import File as SubmissionFileMetadata
|
|
22
23
|
from pydantic import ValidationError
|
|
23
24
|
|
|
25
|
+
from .bam_validation import validate_bam
|
|
24
26
|
from .download import S3BotoDownloadWorker
|
|
25
27
|
from .fastq_validation import validate_paired_end_reads, validate_single_end_reads
|
|
26
28
|
from .file_operations import Crypt4GH, calculate_sha256
|
|
@@ -265,12 +267,16 @@ class Submission:
|
|
|
265
267
|
def find_fastq_files(sequence_data: SequenceData) -> list[File]:
|
|
266
268
|
return [f for f in sequence_data.files if f.file_type == FileType.fastq]
|
|
267
269
|
|
|
270
|
+
def find_bam_files(sequence_data: SequenceData) -> list[File]:
|
|
271
|
+
return [f for f in sequence_data.files if f.file_type == FileType.bam]
|
|
272
|
+
|
|
268
273
|
for donor in self.metadata.content.donors:
|
|
269
274
|
for lab_data in donor.lab_data:
|
|
270
275
|
sequencing_layout = lab_data.sequencing_layout
|
|
271
276
|
sequence_data = lab_data.sequence_data
|
|
272
277
|
# find all FASTQ files
|
|
273
278
|
fastq_files = find_fastq_files(sequence_data)
|
|
279
|
+
bam_files = find_bam_files(sequence_data)
|
|
274
280
|
|
|
275
281
|
match sequencing_layout:
|
|
276
282
|
case SequencingLayout.single_end | SequencingLayout.reverse | SequencingLayout.other:
|
|
@@ -279,16 +285,45 @@ class Submission:
|
|
|
279
285
|
case SequencingLayout.paired_end:
|
|
280
286
|
yield from self._validate_paired_end(fastq_files, progress_logger)
|
|
281
287
|
|
|
288
|
+
yield from self._validate_bams(bam_files, progress_logger)
|
|
289
|
+
|
|
290
|
+
def _validate_bams(
|
|
291
|
+
self,
|
|
292
|
+
bam_files: list[File],
|
|
293
|
+
progress_logger: FileProgressLogger[ValidationState],
|
|
294
|
+
) -> Generator[str, None, None]:
|
|
295
|
+
def validate_file(local_file_path, _file_metadata) -> ValidationState:
|
|
296
|
+
self.__log.debug("Validating '%s'...", str(local_file_path))
|
|
297
|
+
|
|
298
|
+
# validate the file
|
|
299
|
+
errors = list(validate_bam(local_file_path))
|
|
300
|
+
validation_passed = len(errors) == 0
|
|
301
|
+
|
|
302
|
+
# return log state
|
|
303
|
+
return ValidationState(
|
|
304
|
+
errors=errors,
|
|
305
|
+
validation_passed=validation_passed,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
for bam_file in bam_files:
|
|
309
|
+
logged_state = progress_logger.get_state(
|
|
310
|
+
self.files_dir / bam_file.file_path,
|
|
311
|
+
bam_file,
|
|
312
|
+
default=validate_file, # validate the file if the state was not calculated yet
|
|
313
|
+
)
|
|
314
|
+
if logged_state:
|
|
315
|
+
yield from logged_state["errors"]
|
|
316
|
+
|
|
282
317
|
def _validate_single_end(
|
|
283
318
|
self,
|
|
284
319
|
fastq_files: list[File],
|
|
285
320
|
progress_logger: FileProgressLogger[ValidationState],
|
|
286
321
|
) -> Generator[str, None, None]:
|
|
287
|
-
def validate_file(local_file_path,
|
|
322
|
+
def validate_file(local_file_path, file_metadata: SubmissionFileMetadata) -> ValidationState:
|
|
288
323
|
self.__log.debug("Validating '%s'...", str(local_file_path))
|
|
289
324
|
|
|
290
325
|
# validate the file
|
|
291
|
-
errors = list(validate_single_end_reads(local_file_path))
|
|
326
|
+
errors = list(validate_single_end_reads(local_file_path, expected_read_length=file_metadata.read_length))
|
|
292
327
|
validation_passed = len(errors) == 0
|
|
293
328
|
|
|
294
329
|
# return log state
|
|
@@ -339,6 +374,7 @@ class Submission:
|
|
|
339
374
|
validate_paired_end_reads(
|
|
340
375
|
local_fastq_r1_path, # fastq R1
|
|
341
376
|
local_fastq_r2_path, # fastq R2
|
|
377
|
+
expected_read_length=fastq_r1.read_length, # fastq R1 and R2 should have the same read length
|
|
342
378
|
)
|
|
343
379
|
)
|
|
344
380
|
validation_passed = len(errors) == 0
|
|
@@ -493,13 +529,26 @@ class EncryptedSubmission:
|
|
|
493
529
|
|
|
494
530
|
return retval
|
|
495
531
|
|
|
532
|
+
_submission_id: str | None = None
|
|
533
|
+
|
|
534
|
+
@property
|
|
535
|
+
def submission_id(self) -> str:
|
|
536
|
+
"""ID to refer to an individual submission to a GRZ"""
|
|
537
|
+
if self._submission_id is None:
|
|
538
|
+
# generate a submission ID once
|
|
539
|
+
submitter_id = self.metadata.content.submission.submitter_id
|
|
540
|
+
submission_date = self.metadata.content.submission.submission_date
|
|
541
|
+
# use first 8 characters of SHA256 hash of transaction ID to virtually prevent collisions
|
|
542
|
+
suffix = hashlib.sha256(self.metadata.transaction_id.encode("utf-8")).hexdigest()[:8]
|
|
543
|
+
self._submission_id = f"{submitter_id}_{submission_date}_{suffix}"
|
|
544
|
+
|
|
545
|
+
return self._submission_id
|
|
546
|
+
|
|
496
547
|
def get_metadata_file_path_and_object_id(self) -> tuple[Path, str]:
|
|
497
548
|
"""
|
|
498
549
|
:return: tuple with the `local_file_path` and s3_object_id of the metadata file
|
|
499
550
|
"""
|
|
500
|
-
return Path(self.metadata.file_path), str(
|
|
501
|
-
Path(self.metadata.transaction_id) / "metadata" / self.metadata.file_path.name
|
|
502
|
-
)
|
|
551
|
+
return Path(self.metadata.file_path), str(Path(self.submission_id) / "metadata" / self.metadata.file_path.name)
|
|
503
552
|
|
|
504
553
|
def get_encrypted_files_and_object_id(self) -> dict[Path, str]:
|
|
505
554
|
"""
|
|
@@ -508,7 +557,7 @@ class EncryptedSubmission:
|
|
|
508
557
|
retval = {}
|
|
509
558
|
for local_file_path, file_metadata in self.encrypted_files.items():
|
|
510
559
|
retval[local_file_path] = str(
|
|
511
|
-
Path(self.
|
|
560
|
+
Path(self.submission_id) / "files" / self.get_encrypted_file_path(file_metadata.file_path)
|
|
512
561
|
)
|
|
513
562
|
return retval
|
|
514
563
|
|
|
@@ -788,10 +837,9 @@ class Worker:
|
|
|
788
837
|
|
|
789
838
|
return submission
|
|
790
839
|
|
|
791
|
-
def upload(self, config: ConfigModel):
|
|
840
|
+
def upload(self, config: ConfigModel) -> str:
|
|
792
841
|
"""
|
|
793
|
-
Upload an encrypted submission
|
|
794
|
-
|
|
842
|
+
Upload an encrypted submission and return the generated submission ID
|
|
795
843
|
"""
|
|
796
844
|
upload_worker = S3BotoUploadWorker(config, status_file_path=self.progress_file_upload)
|
|
797
845
|
|
|
@@ -799,6 +847,8 @@ class Worker:
|
|
|
799
847
|
|
|
800
848
|
upload_worker.upload(encrypted_submission)
|
|
801
849
|
|
|
850
|
+
return encrypted_submission.submission_id
|
|
851
|
+
|
|
802
852
|
def download(self, config: ConfigModel, submission_id: str):
|
|
803
853
|
"""
|
|
804
854
|
Download an encrypted submission
|
|
@@ -9,7 +9,7 @@ from collections.abc import Callable
|
|
|
9
9
|
from os import PathLike
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
|
-
from grz_pydantic_models.
|
|
12
|
+
from grz_pydantic_models.submission.metadata.v1 import File as SubmissionFileMetadata
|
|
13
13
|
|
|
14
14
|
from .file_operations import read_multiple_json
|
|
15
15
|
from .states import State
|
|
@@ -105,7 +105,8 @@ class FileProgressLogger[T: State]:
|
|
|
105
105
|
:param file_path: The file path to query for the state.
|
|
106
106
|
:param file_metadata: The metadata of the file to query for the state.
|
|
107
107
|
:param default: Default state to use if the file does not exist.
|
|
108
|
-
Can be a Callable that takes the file path and the file metadata as input and returns some state
|
|
108
|
+
Can be a Callable that takes the file path and the file metadata as input and returns some state:
|
|
109
|
+
`Callable[[Path, SubmissionFileMetadata], T]`.
|
|
109
110
|
|
|
110
111
|
The default state gets automatically saved as the state for this file in case there is no stored state.
|
|
111
112
|
:return: A dictionary representing the file's state, or None if the file's state isn't logged.
|
|
@@ -98,8 +98,11 @@ class S3BotoUploadWorker(UploadWorker):
|
|
|
98
98
|
return string
|
|
99
99
|
|
|
100
100
|
# configure proxies if proxy_url is defined
|
|
101
|
-
proxy_url =
|
|
102
|
-
config = Boto3Config(
|
|
101
|
+
proxy_url = self._config.s3_options.proxy_url
|
|
102
|
+
config = Boto3Config(
|
|
103
|
+
proxies={"http": str(proxy_url), "https": str(proxy_url)} if proxy_url is not None else None,
|
|
104
|
+
request_checksum_calculation=self._config.s3_options.request_checksum_calculation,
|
|
105
|
+
)
|
|
103
106
|
|
|
104
107
|
# Initialize S3 client for uploading
|
|
105
108
|
self._s3_client: boto3.session.Session.client = boto3_client(
|
|
@@ -130,7 +133,9 @@ class S3BotoUploadWorker(UploadWorker):
|
|
|
130
133
|
if filesize / MULTIPART_CHUNKSIZE > MULTIPART_MAX_CHUNKS
|
|
131
134
|
else MULTIPART_CHUNKSIZE
|
|
132
135
|
)
|
|
133
|
-
self.__log.debug(
|
|
136
|
+
self.__log.debug(
|
|
137
|
+
f"Using a chunksize of: {chunksize / 1024**2}MiB, results in {math.ceil(filesize / chunksize)} chunk(s)"
|
|
138
|
+
)
|
|
134
139
|
|
|
135
140
|
config = TransferConfig(
|
|
136
141
|
multipart_threshold=MULTIPART_THRESHOLD,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: grz-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Tool for validation, encryption and upload of MV submissions to GDCs.
|
|
5
5
|
Author-email: Koray Kirli <koraykirli@gmail.com>, Mathias Lesche <mathias.lesche@tu-dresden.de>, "Florian R. Hölzlwimmer" <git.ich@frhoelzlwimmer.de>, Till Hartmann <till.hartmann@bih-charite.de>, Thomas Sell <thomas.sell@bih-charite.de>
|
|
6
6
|
License: MIT
|
|
@@ -11,7 +11,7 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
11
11
|
Classifier: Operating System :: OS Independent
|
|
12
12
|
Requires-Python: >=3.12
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
|
-
Requires-Dist: boto3<2,>=1.
|
|
14
|
+
Requires-Dist: boto3<2,>=1.36
|
|
15
15
|
Requires-Dist: click<9,>=8.1.7
|
|
16
16
|
Requires-Dist: crypt4gh<2,>=1.7
|
|
17
17
|
Requires-Dist: jsonschema<5,>=4.23.0
|
|
@@ -19,7 +19,8 @@ Requires-Dist: pyyaml<7,>=6.0.2
|
|
|
19
19
|
Requires-Dist: tqdm<5,>=4.66.5
|
|
20
20
|
Requires-Dist: pydantic<2.10,>=2.9.2
|
|
21
21
|
Requires-Dist: platformdirs<5,>=4.3.6
|
|
22
|
-
Requires-Dist: grz-pydantic-models>=1.2.
|
|
22
|
+
Requires-Dist: grz-pydantic-models>=1.2.1
|
|
23
|
+
Requires-Dist: pysam==0.23.*
|
|
23
24
|
Provides-Extra: test
|
|
24
25
|
Requires-Dist: pytest<9,>=8.3.3; extra == "test"
|
|
25
26
|
Requires-Dist: pytest-cov<6,>=5.0.0; extra == "test"
|
|
@@ -254,6 +255,8 @@ Option is for the usage at a GDC/GRZ.
|
|
|
254
255
|
|
|
255
256
|
## Testing
|
|
256
257
|
|
|
258
|
+
Please note that binary files used for testing are managed with [Git LFS](https://git-lfs.com), which will be needed to clone them locally with the git repository.
|
|
259
|
+
|
|
257
260
|
To run the tests, navigate to the root directory of your project and invoke `pytest`.
|
|
258
261
|
Alternatively, install `uv` and `tox` and run `uv run tox`.
|
|
259
262
|
|
|
@@ -4,6 +4,7 @@ README.md
|
|
|
4
4
|
pyproject.toml
|
|
5
5
|
src/__init__.py
|
|
6
6
|
src/grz_cli/__init__.py
|
|
7
|
+
src/grz_cli/bam_validation.py
|
|
7
8
|
src/grz_cli/cli.py
|
|
8
9
|
src/grz_cli/constants.py
|
|
9
10
|
src/grz_cli/download.py
|
|
@@ -22,7 +23,7 @@ src/grz_cli.egg-info/requires.txt
|
|
|
22
23
|
src/grz_cli.egg-info/top_level.txt
|
|
23
24
|
src/grz_cli/models/__init__.py
|
|
24
25
|
src/grz_cli/models/config.py
|
|
25
|
-
|
|
26
|
+
tests/test_bam_validation.py
|
|
26
27
|
tests/test_cli.py
|
|
27
28
|
tests/test_download.py
|
|
28
29
|
tests/test_fastq_validation.py
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
boto3<2,>=1.
|
|
1
|
+
boto3<2,>=1.36
|
|
2
2
|
click<9,>=8.1.7
|
|
3
3
|
crypt4gh<2,>=1.7
|
|
4
4
|
jsonschema<5,>=4.23.0
|
|
@@ -6,7 +6,8 @@ pyyaml<7,>=6.0.2
|
|
|
6
6
|
tqdm<5,>=4.66.5
|
|
7
7
|
pydantic<2.10,>=2.9.2
|
|
8
8
|
platformdirs<5,>=4.3.6
|
|
9
|
-
grz-pydantic-models>=1.2.
|
|
9
|
+
grz-pydantic-models>=1.2.1
|
|
10
|
+
pysam==0.23.*
|
|
10
11
|
|
|
11
12
|
[test]
|
|
12
13
|
pytest<9,>=8.3.3
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Tests for the bam_validation module."""
|
|
2
|
+
|
|
3
|
+
import importlib.resources
|
|
4
|
+
|
|
5
|
+
from grz_cli.bam_validation import validate_bam
|
|
6
|
+
|
|
7
|
+
from . import resources
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_valid_hifi_bam():
|
|
11
|
+
"""Valid HiFi BAM files should pass validation"""
|
|
12
|
+
bam_ptr = importlib.resources.files(resources).joinpath("reads", "valid_HiFi.bam")
|
|
13
|
+
with importlib.resources.as_file(bam_ptr) as bam_path:
|
|
14
|
+
errors = list(validate_bam(bam_path))
|
|
15
|
+
assert len(errors) == 0
|
|
@@ -183,7 +183,6 @@ def test_upload_download_submission(
|
|
|
183
183
|
remote_bucket,
|
|
184
184
|
):
|
|
185
185
|
submission_dir = Path("tests/mock_files/submissions/valid_submission")
|
|
186
|
-
transaction_id = "aaaaaaaa00000000aaaaaaaa00000000aaaaaaaa00000000aaaaaaaa00000000"
|
|
187
186
|
|
|
188
187
|
shutil.copytree(
|
|
189
188
|
submission_dir / "encrypted_files",
|
|
@@ -200,7 +199,7 @@ def test_upload_download_submission(
|
|
|
200
199
|
"--config-file",
|
|
201
200
|
temp_config_file_path,
|
|
202
201
|
]
|
|
203
|
-
runner = CliRunner()
|
|
202
|
+
runner = CliRunner(mix_stderr=False)
|
|
204
203
|
with mock.patch(
|
|
205
204
|
"grz_cli.models.config.S3Options.__getattr__",
|
|
206
205
|
lambda self, name: None if name == "endpoint_url" else AttributeError,
|
|
@@ -208,6 +207,9 @@ def test_upload_download_submission(
|
|
|
208
207
|
result = runner.invoke(grz_cli.cli.cli, testargs, catch_exceptions=False)
|
|
209
208
|
|
|
210
209
|
assert result.exit_code == 0, result.output
|
|
210
|
+
assert len(result.output) != 0, result.stderr
|
|
211
|
+
|
|
212
|
+
submission_id = result.output.strip()
|
|
211
213
|
|
|
212
214
|
# download
|
|
213
215
|
download_dir = tmpdir_factory.mktemp("submission_download")
|
|
@@ -217,7 +219,7 @@ def test_upload_download_submission(
|
|
|
217
219
|
testargs = [
|
|
218
220
|
"download",
|
|
219
221
|
"--submission-id",
|
|
220
|
-
|
|
222
|
+
submission_id,
|
|
221
223
|
"--output-dir",
|
|
222
224
|
str(download_dir_path),
|
|
223
225
|
"--config-file",
|
|
@@ -5,7 +5,7 @@ import json
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
7
|
import pytest
|
|
8
|
-
from grz_pydantic_models.
|
|
8
|
+
from grz_pydantic_models.submission.metadata.v1 import File as SubmissionFileMetadata
|
|
9
9
|
|
|
10
10
|
from grz_cli.file_operations import read_multiple_json
|
|
11
11
|
from grz_cli.progress_logging import FileProgressLogger
|
|
@@ -62,6 +62,7 @@ def test_boto_upload(
|
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
def test__gather_files_to_upload(encrypted_submission):
|
|
65
|
+
submission_id = encrypted_submission.submission_id
|
|
65
66
|
metadata_file_path, metadata_s3_object_id = encrypted_submission.get_metadata_file_path_and_object_id()
|
|
66
67
|
gathered_files = encrypted_submission.get_encrypted_files_and_object_id()
|
|
67
68
|
gathered_files[metadata_file_path] = metadata_s3_object_id
|
|
@@ -70,47 +71,47 @@ def test__gather_files_to_upload(encrypted_submission):
|
|
|
70
71
|
expected_files = [
|
|
71
72
|
(
|
|
72
73
|
"tests/mock_files/submissions/valid_submission/encrypted_files/target_regions.bed.c4gh",
|
|
73
|
-
"
|
|
74
|
+
f"{submission_id}/files/target_regions.bed.c4gh",
|
|
74
75
|
),
|
|
75
76
|
(
|
|
76
77
|
"tests/mock_files/submissions/valid_submission/encrypted_files/aaaaaaaa00000000aaaaaaaa00000000aaaaaaaa00000000aaaaaaaa00000000_blood_normal.read1.fastq.gz.c4gh",
|
|
77
|
-
"
|
|
78
|
+
f"{submission_id}/files/aaaaaaaa00000000aaaaaaaa00000000aaaaaaaa00000000aaaaaaaa00000000_blood_normal.read1.fastq.gz.c4gh",
|
|
78
79
|
),
|
|
79
80
|
(
|
|
80
81
|
"tests/mock_files/submissions/valid_submission/encrypted_files/aaaaaaaa00000000aaaaaaaa00000000aaaaaaaa00000000aaaaaaaa00000000_blood_normal.read2.fastq.gz.c4gh",
|
|
81
|
-
"
|
|
82
|
+
f"{submission_id}/files/aaaaaaaa00000000aaaaaaaa00000000aaaaaaaa00000000aaaaaaaa00000000_blood_normal.read2.fastq.gz.c4gh",
|
|
82
83
|
),
|
|
83
84
|
(
|
|
84
85
|
"tests/mock_files/submissions/valid_submission/encrypted_files/aaaaaaaa00000000aaaaaaaa00000000aaaaaaaa00000000aaaaaaaa00000000_blood_normal.vcf.c4gh",
|
|
85
|
-
"
|
|
86
|
+
f"{submission_id}/files/aaaaaaaa00000000aaaaaaaa00000000aaaaaaaa00000000aaaaaaaa00000000_blood_normal.vcf.c4gh",
|
|
86
87
|
),
|
|
87
88
|
(
|
|
88
89
|
"tests/mock_files/submissions/valid_submission/encrypted_files/aaaaaaaa00000000aaaaaaaa00000000aaaaaaaa00000000aaaaaaaa00000000_blood_tumor.read1.fastq.gz.c4gh",
|
|
89
|
-
"
|
|
90
|
+
f"{submission_id}/files/aaaaaaaa00000000aaaaaaaa00000000aaaaaaaa00000000aaaaaaaa00000000_blood_tumor.read1.fastq.gz.c4gh",
|
|
90
91
|
),
|
|
91
92
|
(
|
|
92
93
|
"tests/mock_files/submissions/valid_submission/encrypted_files/aaaaaaaa00000000aaaaaaaa00000000aaaaaaaa00000000aaaaaaaa00000000_blood_tumor.read2.fastq.gz.c4gh",
|
|
93
|
-
"
|
|
94
|
+
f"{submission_id}/files/aaaaaaaa00000000aaaaaaaa00000000aaaaaaaa00000000aaaaaaaa00000000_blood_tumor.read2.fastq.gz.c4gh",
|
|
94
95
|
),
|
|
95
96
|
(
|
|
96
97
|
"tests/mock_files/submissions/valid_submission/encrypted_files/aaaaaaaa00000000aaaaaaaa00000000aaaaaaaa00000000aaaaaaaa00000000_blood_tumor.vcf.c4gh",
|
|
97
|
-
"
|
|
98
|
+
f"{submission_id}/files/aaaaaaaa00000000aaaaaaaa00000000aaaaaaaa00000000aaaaaaaa00000000_blood_tumor.vcf.c4gh",
|
|
98
99
|
),
|
|
99
100
|
(
|
|
100
101
|
"tests/mock_files/submissions/valid_submission/encrypted_files/bbbbbbbb11111111bbbbbbbb11111111bbbbbbbb11111111bbbbbbbb11111111_blood_normal.read1.fastq.gz.c4gh",
|
|
101
|
-
"
|
|
102
|
+
f"{submission_id}/files/bbbbbbbb11111111bbbbbbbb11111111bbbbbbbb11111111bbbbbbbb11111111_blood_normal.read1.fastq.gz.c4gh",
|
|
102
103
|
),
|
|
103
104
|
(
|
|
104
105
|
"tests/mock_files/submissions/valid_submission/encrypted_files/bbbbbbbb11111111bbbbbbbb11111111bbbbbbbb11111111bbbbbbbb11111111_blood_normal.read2.fastq.gz.c4gh",
|
|
105
|
-
"
|
|
106
|
+
f"{submission_id}/files/bbbbbbbb11111111bbbbbbbb11111111bbbbbbbb11111111bbbbbbbb11111111_blood_normal.read2.fastq.gz.c4gh",
|
|
106
107
|
),
|
|
107
108
|
(
|
|
108
109
|
"tests/mock_files/submissions/valid_submission/encrypted_files/bbbbbbbb11111111bbbbbbbb11111111bbbbbbbb11111111bbbbbbbb11111111_blood_normal.vcf.c4gh",
|
|
109
|
-
"
|
|
110
|
+
f"{submission_id}/files/bbbbbbbb11111111bbbbbbbb11111111bbbbbbbb11111111bbbbbbbb11111111_blood_normal.vcf.c4gh",
|
|
110
111
|
),
|
|
111
112
|
(
|
|
112
113
|
"tests/mock_files/submissions/valid_submission/metadata/metadata.json",
|
|
113
|
-
"
|
|
114
|
+
f"{submission_id}/metadata/metadata.json",
|
|
114
115
|
),
|
|
115
116
|
]
|
|
116
117
|
expected_files = sorted(expected_files)
|
grz_cli-0.3.0/MANIFEST.in
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.3.0"
|
|
@@ -1,299 +0,0 @@
|
|
|
1
|
-
[
|
|
2
|
-
{
|
|
3
|
-
"libraryType": "panel",
|
|
4
|
-
"sequenceSubtype": "somatic",
|
|
5
|
-
"genomicStudySubtype": "tumor+germline",
|
|
6
|
-
"thresholds": {
|
|
7
|
-
"meanDepthOfCoverage": 200,
|
|
8
|
-
"readLength": 100,
|
|
9
|
-
"fractionBasesAboveQualityThreshold": {
|
|
10
|
-
"qualityThreshold": 30,
|
|
11
|
-
"fractionBasesAbove": 0.85
|
|
12
|
-
},
|
|
13
|
-
"targetedRegionsAboveMinCoverage": {
|
|
14
|
-
"minCoverage": 100,
|
|
15
|
-
"fractionAbove": 0.8
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
"libraryType": "panel",
|
|
21
|
-
"sequenceSubtype": "somatic",
|
|
22
|
-
"genomicStudySubtype": "tumor-only",
|
|
23
|
-
"thresholds": {
|
|
24
|
-
"meanDepthOfCoverage": 200,
|
|
25
|
-
"readLength": 100,
|
|
26
|
-
"fractionBasesAboveQualityThreshold": {
|
|
27
|
-
"qualityThreshold": 30,
|
|
28
|
-
"fractionBasesAbove": 0.85
|
|
29
|
-
},
|
|
30
|
-
"targetedRegionsAboveMinCoverage": {
|
|
31
|
-
"minCoverage": 100,
|
|
32
|
-
"fractionAbove": 0.8
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
"libraryType": "wes",
|
|
38
|
-
"sequenceSubtype": "somatic",
|
|
39
|
-
"genomicStudySubtype": "tumor+germline",
|
|
40
|
-
"thresholds": {
|
|
41
|
-
"meanDepthOfCoverage": 200,
|
|
42
|
-
"readLength": 100,
|
|
43
|
-
"fractionBasesAboveQualityThreshold": {
|
|
44
|
-
"qualityThreshold": 30,
|
|
45
|
-
"fractionBasesAbove": 0.85
|
|
46
|
-
},
|
|
47
|
-
"targetedRegionsAboveMinCoverage": {
|
|
48
|
-
"minCoverage": 100,
|
|
49
|
-
"fractionAbove": 0.8
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
"libraryType": "wes",
|
|
55
|
-
"sequenceSubtype": "somatic",
|
|
56
|
-
"genomicStudySubtype": "tumor-only",
|
|
57
|
-
"thresholds": {
|
|
58
|
-
"meanDepthOfCoverage": 200,
|
|
59
|
-
"readLength": 100,
|
|
60
|
-
"fractionBasesAboveQualityThreshold": {
|
|
61
|
-
"qualityThreshold": 30,
|
|
62
|
-
"fractionBasesAbove": 0.85
|
|
63
|
-
},
|
|
64
|
-
"targetedRegionsAboveMinCoverage": {
|
|
65
|
-
"minCoverage": 100,
|
|
66
|
-
"fractionAbove": 0.8
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
"libraryType": "wes",
|
|
72
|
-
"sequenceSubtype": "germline",
|
|
73
|
-
"genomicStudySubtype": "tumor+germline",
|
|
74
|
-
"thresholds": {
|
|
75
|
-
"meanDepthOfCoverage": 100,
|
|
76
|
-
"readLength": 100,
|
|
77
|
-
"fractionBasesAboveQualityThreshold": {
|
|
78
|
-
"qualityThreshold": 30,
|
|
79
|
-
"fractionBasesAbove": 0.85
|
|
80
|
-
},
|
|
81
|
-
"targetedRegionsAboveMinCoverage": {
|
|
82
|
-
"minCoverage": 30,
|
|
83
|
-
"fractionAbove": 0.8
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
"libraryType": "wgs",
|
|
89
|
-
"sequenceSubtype": "somatic",
|
|
90
|
-
"genomicStudySubtype": "tumor+germline",
|
|
91
|
-
"thresholds": {
|
|
92
|
-
"meanDepthOfCoverage": 100,
|
|
93
|
-
"readLength": 100,
|
|
94
|
-
"fractionBasesAboveQualityThreshold": {
|
|
95
|
-
"qualityThreshold": 30,
|
|
96
|
-
"fractionBasesAbove": 0.85
|
|
97
|
-
},
|
|
98
|
-
"targetedRegionsAboveMinCoverage": {
|
|
99
|
-
"minCoverage": 30,
|
|
100
|
-
"fractionAbove": 0.8
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
"libraryType": "wgs",
|
|
106
|
-
"sequenceSubtype": "somatic",
|
|
107
|
-
"genomicStudySubtype": "tumor-only",
|
|
108
|
-
"thresholds": {
|
|
109
|
-
"meanDepthOfCoverage": 100,
|
|
110
|
-
"readLength": 100,
|
|
111
|
-
"fractionBasesAboveQualityThreshold": {
|
|
112
|
-
"qualityThreshold": 30,
|
|
113
|
-
"fractionBasesAbove": 0.85
|
|
114
|
-
},
|
|
115
|
-
"targetedRegionsAboveMinCoverage": {
|
|
116
|
-
"minCoverage": 30,
|
|
117
|
-
"fractionAbove": 0.8
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
"libraryType": "wgs",
|
|
123
|
-
"sequenceSubtype": "germline",
|
|
124
|
-
"genomicStudySubtype": "tumor+germline",
|
|
125
|
-
"thresholds": {
|
|
126
|
-
"meanDepthOfCoverage": 30,
|
|
127
|
-
"readLength": 100,
|
|
128
|
-
"fractionBasesAboveQualityThreshold": {
|
|
129
|
-
"qualityThreshold": 30,
|
|
130
|
-
"fractionBasesAbove": 0.85
|
|
131
|
-
},
|
|
132
|
-
"targetedRegionsAboveMinCoverage": {
|
|
133
|
-
"minCoverage": 20,
|
|
134
|
-
"fractionAbove": 0.8
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
"libraryType": "wgs",
|
|
140
|
-
"sequenceSubtype": "germline",
|
|
141
|
-
"genomicStudySubtype": "germline-only",
|
|
142
|
-
"thresholds": {
|
|
143
|
-
"meanDepthOfCoverage": 30,
|
|
144
|
-
"readLength": 100,
|
|
145
|
-
"fractionBasesAboveQualityThreshold": {
|
|
146
|
-
"qualityThreshold": 30,
|
|
147
|
-
"fractionBasesAbove": 0.85
|
|
148
|
-
},
|
|
149
|
-
"targetedRegionsAboveMinCoverage": {
|
|
150
|
-
"minCoverage": 20,
|
|
151
|
-
"fractionAbove": 0.8
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
},
|
|
155
|
-
{
|
|
156
|
-
"libraryType": "panel_lr",
|
|
157
|
-
"sequenceSubtype": "somatic",
|
|
158
|
-
"genomicStudySubtype": "tumor+germline",
|
|
159
|
-
"thresholds": {
|
|
160
|
-
"meanDepthOfCoverage": 200,
|
|
161
|
-
"fractionBasesAboveQualityThreshold": {
|
|
162
|
-
"qualityThreshold": 20,
|
|
163
|
-
"fractionBasesAbove": 0.8
|
|
164
|
-
},
|
|
165
|
-
"targetedRegionsAboveMinCoverage": {
|
|
166
|
-
"minCoverage": 100,
|
|
167
|
-
"fractionAbove": 0.8
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
},
|
|
171
|
-
{
|
|
172
|
-
"libraryType": "panel_lr",
|
|
173
|
-
"sequenceSubtype": "somatic",
|
|
174
|
-
"genomicStudySubtype": "tumor-only",
|
|
175
|
-
"thresholds": {
|
|
176
|
-
"meanDepthOfCoverage": 200,
|
|
177
|
-
"fractionBasesAboveQualityThreshold": {
|
|
178
|
-
"qualityThreshold": 20,
|
|
179
|
-
"fractionBasesAbove": 0.8
|
|
180
|
-
},
|
|
181
|
-
"targetedRegionsAboveMinCoverage": {
|
|
182
|
-
"minCoverage": 100,
|
|
183
|
-
"fractionAbove": 0.8
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
},
|
|
187
|
-
{
|
|
188
|
-
"libraryType": "wes_lr",
|
|
189
|
-
"sequenceSubtype": "somatic",
|
|
190
|
-
"genomicStudySubtype": "tumor+germline",
|
|
191
|
-
"thresholds": {
|
|
192
|
-
"meanDepthOfCoverage": 200,
|
|
193
|
-
"fractionBasesAboveQualityThreshold": {
|
|
194
|
-
"qualityThreshold": 20,
|
|
195
|
-
"fractionBasesAbove": 0.8
|
|
196
|
-
},
|
|
197
|
-
"targetedRegionsAboveMinCoverage": {
|
|
198
|
-
"minCoverage": 100,
|
|
199
|
-
"fractionAbove": 0.8
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
},
|
|
203
|
-
{
|
|
204
|
-
"libraryType": "wes_lr",
|
|
205
|
-
"sequenceSubtype": "somatic",
|
|
206
|
-
"genomicStudySubtype": "tumor-only",
|
|
207
|
-
"thresholds": {
|
|
208
|
-
"meanDepthOfCoverage": 200,
|
|
209
|
-
"fractionBasesAboveQualityThreshold": {
|
|
210
|
-
"qualityThreshold": 20,
|
|
211
|
-
"fractionBasesAbove": 0.8
|
|
212
|
-
},
|
|
213
|
-
"targetedRegionsAboveMinCoverage": {
|
|
214
|
-
"minCoverage": 100,
|
|
215
|
-
"fractionAbove": 0.8
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
},
|
|
219
|
-
{
|
|
220
|
-
"libraryType": "wes_lr",
|
|
221
|
-
"sequenceSubtype": "germline",
|
|
222
|
-
"genomicStudySubtype": "tumor+germline",
|
|
223
|
-
"thresholds": {
|
|
224
|
-
"meanDepthOfCoverage": 100,
|
|
225
|
-
"fractionBasesAboveQualityThreshold": {
|
|
226
|
-
"qualityThreshold": 20,
|
|
227
|
-
"fractionBasesAbove": 0.8
|
|
228
|
-
},
|
|
229
|
-
"targetedRegionsAboveMinCoverage": {
|
|
230
|
-
"minCoverage": 30,
|
|
231
|
-
"fractionAbove": 0.8
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
},
|
|
235
|
-
{
|
|
236
|
-
"libraryType": "wgs_lr",
|
|
237
|
-
"sequenceSubtype": "somatic",
|
|
238
|
-
"genomicStudySubtype": "tumor+germline",
|
|
239
|
-
"thresholds": {
|
|
240
|
-
"meanDepthOfCoverage": 100,
|
|
241
|
-
"fractionBasesAboveQualityThreshold": {
|
|
242
|
-
"qualityThreshold": 20,
|
|
243
|
-
"fractionBasesAbove": 0.8
|
|
244
|
-
},
|
|
245
|
-
"targetedRegionsAboveMinCoverage": {
|
|
246
|
-
"minCoverage": 30,
|
|
247
|
-
"fractionAbove": 0.8
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
},
|
|
251
|
-
{
|
|
252
|
-
"libraryType": "wgs_lr",
|
|
253
|
-
"sequenceSubtype": "somatic",
|
|
254
|
-
"genomicStudySubtype": "tumor-only",
|
|
255
|
-
"thresholds": {
|
|
256
|
-
"meanDepthOfCoverage": 100,
|
|
257
|
-
"fractionBasesAboveQualityThreshold": {
|
|
258
|
-
"qualityThreshold": 20,
|
|
259
|
-
"fractionBasesAbove": 0.8
|
|
260
|
-
},
|
|
261
|
-
"targetedRegionsAboveMinCoverage": {
|
|
262
|
-
"minCoverage": 30,
|
|
263
|
-
"fractionAbove": 0.8
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
},
|
|
267
|
-
{
|
|
268
|
-
"libraryType": "wgs_lr",
|
|
269
|
-
"sequenceSubtype": "germline",
|
|
270
|
-
"genomicStudySubtype": "tumor+germline",
|
|
271
|
-
"thresholds": {
|
|
272
|
-
"meanDepthOfCoverage": 30,
|
|
273
|
-
"fractionBasesAboveQualityThreshold": {
|
|
274
|
-
"qualityThreshold": 20,
|
|
275
|
-
"fractionBasesAbove": 0.8
|
|
276
|
-
},
|
|
277
|
-
"targetedRegionsAboveMinCoverage": {
|
|
278
|
-
"minCoverage": 20,
|
|
279
|
-
"fractionAbove": 0.8
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
},
|
|
283
|
-
{
|
|
284
|
-
"libraryType": "wgs_lr",
|
|
285
|
-
"sequenceSubtype": "germline",
|
|
286
|
-
"genomicStudySubtype": "germline-only",
|
|
287
|
-
"thresholds": {
|
|
288
|
-
"meanDepthOfCoverage": 30,
|
|
289
|
-
"fractionBasesAboveQualityThreshold": {
|
|
290
|
-
"qualityThreshold": 20,
|
|
291
|
-
"fractionBasesAbove": 0.8
|
|
292
|
-
},
|
|
293
|
-
"targetedRegionsAboveMinCoverage": {
|
|
294
|
-
"minCoverage": 20,
|
|
295
|
-
"fractionAbove": 0.8
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|