phc-ingestion 0.8.36__py3-none-any.whl → 0.8.38__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.
@@ -84,11 +84,17 @@ def process_caris_json(infile: str, outpath: str, file_name: str, source_file_id
84
84
  ]
85
85
 
86
86
  # Get patient
87
- metadata = extract_metadata(data, file_name, files, source_file_id, log)
87
+ metadata, is_test_cancelled_permit_vcf_skip = extract_metadata(
88
+ data, file_name, files, source_file_id, log
89
+ )
88
90
  structural_results = extract_structural(file_name, data, log)
89
91
  cnv_results = extract_cnv(file_name, data, log)
90
92
  rgel_results = convert_tsv_to_rgel(file_name, files, log)
91
- vcf_results = extract_sv(file_name, bool(somatic_filename), bool(germline_filename))
93
+
94
+ include_empty = metadata["ihcTests"] and is_test_cancelled_permit_vcf_skip
95
+ vcf_results = extract_sv(
96
+ file_name, bool(somatic_filename), bool(germline_filename), include_empty
97
+ )
92
98
 
93
99
  # We might not have any of these files but we need an empty json object here.
94
100
  file_genome_references = {}
@@ -117,5 +123,7 @@ def process_caris_json(infile: str, outpath: str, file_name: str, source_file_id
117
123
  result["somatic_vcf"] = f"{outpath}/{somatic_filename}"
118
124
  if germline_filename is not None:
119
125
  result["germline_vcf"] = f"{outpath}/{germline_filename}"
126
+ if not germline_filename and not somatic_filename and include_empty:
127
+ result["somatic_vcf"] = f"{outpath}/{file_name}.modified.somatic.vcf.gz"
120
128
 
121
129
  return (result, germline_case_id, file_genome_references, data)
@@ -80,6 +80,7 @@ def is_valid_test_entry(test: dict):
80
80
 
81
81
  # Build up the manifest iteratively because almost everything is optional
82
82
  def extract_metadata(data, prefix, files, source_file_id, log: Logger) -> dict:
83
+ is_test_cancelled_permit_vcf_skip = False
83
84
  metadata = {}
84
85
 
85
86
  test_details = data["testDetails"]
@@ -158,6 +159,13 @@ def extract_metadata(data, prefix, files, source_file_id, log: Logger) -> dict:
158
159
  # if not sufficient quantity we won't have test results
159
160
  if test_details["reportType"] != "QNS":
160
161
  for test in tests:
162
+ if "test_cancellation_reason" in test:
163
+ if test["test_cancellation_reason"] == "Quantitation quantity not sufficient":
164
+ # capture cancellation reason before bailing
165
+ # this is so we can generate an empty vcf so present biomarkers are
166
+ # still ingested: https://lifeomic.atlassian.net/browse/PHC-5748
167
+ is_test_cancelled_permit_vcf_skip = True
168
+
161
169
  if not is_valid_test_entry(test):
162
170
  continue
163
171
  # Sometimes, if there is only a single test result,
@@ -244,4 +252,4 @@ def extract_metadata(data, prefix, files, source_file_id, log: Logger) -> dict:
244
252
  )
245
253
 
246
254
  active_metadata = {k: v for k, v in metadata.items() if v is not None}
247
- return active_metadata
255
+ return (active_metadata, is_test_cancelled_permit_vcf_skip)
@@ -1,11 +1,5 @@
1
1
  import datetime
2
2
  import gzip
3
- import io
4
- import os
5
- import re
6
- import subprocess
7
- import sys
8
- import zipfile
9
3
 
10
4
  from logging import Logger
11
5
 
@@ -13,8 +7,38 @@ from ingestion.caris.util.tests import safely_extract_tests_from_json_data
13
7
  from ingestion.vcf_standardization.standardize import standardize_vcf
14
8
 
15
9
 
10
+ def create_empty_vcf_zip(prefix):
11
+ vcf_gzip_path = f"{prefix}.modified.somatic.vcf.gz"
12
+ content = (
13
+ """##fileformat=VCFv4.1
14
+ ##filedate="""
15
+ + datetime.datetime.now().isoformat()
16
+ + """
17
+ ##FILTER=<ID=PASS,Description="All filters passed">
18
+ ##FILTER=<ID=R8,Description="IndelRepeatLength is greater than 8">
19
+ ##FILTER=<ID=R8.1,Description="IndelRepeatLength of a monomer is greater than 8">
20
+ ##FILTER=<ID=R8.2,Description="IndelRepeatLength of a dimer is greater than 8">
21
+ ##FILTER=<ID=sb,Description="Variant strand bias high">
22
+ ##FILTER=<ID=sb.s,Description="Variant strand bias significantly high (only for SNV)">
23
+ ##FILTER=<ID=rs,Description="Variant with rs (dbSNP) number in a non-core gene">
24
+ ##FILTER=<ID=FP,Description="Possibly false positives due to high similarity to off-target regions">
25
+ ##FILTER=<ID=NC,Description="Noncoding INDELs on non-core genes">
26
+ ##FILTER=<ID=lowDP,Description="low depth variant">
27
+ ##FILTER=<ID=Benign,Description="Benign variant">
28
+ ##FORMAT=<ID=GT,Number=1,Type=String,Description="Genotype">
29
+ ##FORMAT=<ID=AF,Number=1,Type=String,Description="Variant Allele Frequency">
30
+ #CHROM POS ID REF ALT QUAL FILTER INFO FORMAT """
31
+ + prefix
32
+ + """
33
+ """
34
+ )
35
+
36
+ with gzip.open(vcf_gzip_path, "wb") as f:
37
+ f.write(content.encode("utf-8"))
38
+
39
+
16
40
  # This is done in next step, we are just adding to yaml
17
- def extract_sv(prefix, include_somatic: bool, include_germline: bool):
41
+ def extract_sv(prefix, include_somatic: bool, include_germline: bool, include_empty: bool):
18
42
  vcfs = []
19
43
 
20
44
  # Hard-code genome reference for Caris VCFs
@@ -40,6 +64,17 @@ def extract_sv(prefix, include_somatic: bool, include_germline: bool):
40
64
  }
41
65
  )
42
66
 
67
+ if not vcfs and include_empty:
68
+ create_empty_vcf_zip(prefix)
69
+ vcfs.append(
70
+ {
71
+ "fileName": f".lifeomic/caris/{prefix}/{prefix}.modified.somatic.nrm.filtered.vcf.gz",
72
+ "sequenceType": "somatic",
73
+ "type": "shortVariant",
74
+ "reference": genome_reference,
75
+ }
76
+ )
77
+
43
78
  return vcfs
44
79
 
45
80
 
@@ -1,6 +1,5 @@
1
1
  import gzip
2
2
  import xmltodict
3
- from natsort import natsorted
4
3
  from logging import Logger
5
4
  import re
6
5
  import os
@@ -68,6 +68,7 @@ def process(
68
68
  bool(structural_path_name),
69
69
  translocations,
70
70
  hyperdiploidy_chromosomes,
71
+ log,
71
72
  )
72
73
  pre_filtered_somatic_vcf_path = pre_filter_somatic_vcf(
73
74
  vendor_files["somaticVcfFile"],
@@ -36,7 +36,53 @@ def parse_report_id(line: str) -> str:
36
36
  return parse_pattern(r"^.*Accession #: (.*?) .*$", line, "report ID")
37
37
 
38
38
 
39
- def parse_report_date(line: str) -> str:
39
+ def parse_report_date_single_line(line: str) -> str:
40
40
  return parse_pattern(
41
41
  r"^.*Diagnostic Genomics Laboratory.*(\d{2}\/\d{2}\/\d{4}).*$", line, "report date"
42
42
  )
43
+
44
+
45
+ def parse_report_date_multiline(patient_info_lines: list[str]) -> str:
46
+ in_range_trigger = False
47
+
48
+ for line in patient_info_lines:
49
+ if "Laboratory" in line:
50
+ in_range_trigger = True
51
+ continue
52
+ if in_range_trigger:
53
+ formatted_line = re.sub(r"<\/?T.\/?>", "", line).strip()
54
+ if not formatted_line:
55
+ continue
56
+ return parse_pattern(
57
+ r"^.*(\d{2}\/\d{2}\/\d{4}).*$", formatted_line, "report date from multiline"
58
+ )
59
+
60
+ raise ValueError("Could not parse report date from lines")
61
+
62
+
63
+ def parse_report_date(patient_info_lines: list[str], log: Logger) -> str:
64
+ """
65
+ Typically, the report date is in a form like:
66
+ ```
67
+ Diagnostic Genomics Laboratory 01/01/2021
68
+ ```
69
+
70
+ However, sometimes the date is split across multiple lines, like:
71
+ ```
72
+ Diagnostic Genomics Laboratory
73
+ ...random empty lines or lines with only tags...
74
+ 01/01/2021
75
+ ```
76
+ This function attempts to first parse the date from a single line, and if that fails,
77
+ it will attempt to parse it from multiple lines.
78
+ """
79
+ for line in patient_info_lines:
80
+ if "Laboratory" in line:
81
+ try:
82
+ report_date = parse_report_date_single_line(line)
83
+ return report_date
84
+ except ValueError:
85
+ log.warning("Could not parse report date from single line")
86
+ break
87
+
88
+ return parse_report_date_multiline(patient_info_lines)
@@ -17,7 +17,7 @@ def search_and_grab(array: list, search_item: str, grab_index: int):
17
17
  return array[array.index([i for i in array if re.search(search_item, i)][0]) + grab_index]
18
18
 
19
19
 
20
- def extract_xml_text(xml_in_file: str):
20
+ def extract_xml_text(xml_in_file: str) -> list[str]:
21
21
  with open(xml_in_file, "r") as f:
22
22
  xml_lines = f.readlines()
23
23
 
@@ -35,7 +35,7 @@ def extract_xml_text(xml_in_file: str):
35
35
  return patient_info_lines
36
36
 
37
37
 
38
- def extract_interpretation_text(xml_in_file: str):
38
+ def extract_interpretation_text(xml_in_file: str) -> list[str]:
39
39
  with open(xml_in_file, "r") as f:
40
40
  xml_lines = f.readlines()
41
41
 
@@ -111,7 +111,7 @@ def extract_patient_data(patient_info_lines: list[str]):
111
111
  return patient_data
112
112
 
113
113
 
114
- def extract_test_data(patient_info_lines: list, interpretation_lines: list):
114
+ def extract_test_data(patient_info_lines: list[str], interpretation_lines: list[str], log: Logger):
115
115
  # Initialize manifest and hard-code some values
116
116
  manifest: dict[str, Any] = {}
117
117
  manifest["testType"] = "Plasma Cell Myeloma Panel"
@@ -128,12 +128,11 @@ def extract_test_data(patient_info_lines: list, interpretation_lines: list):
128
128
  manifest["medFacilID"] = ""
129
129
  manifest["medFacilName"] = "IU Health"
130
130
 
131
- for line in patient_info_lines:
132
- if "reportDate" not in manifest and "Laboratory" in line:
133
- report_date = manifest_helpers.parse_report_date(line)
134
- manifest["reportDate"] = transform_date(report_date)
135
- manifest["indexedDate"] = manifest["reportDate"]
131
+ report_date = manifest_helpers.parse_report_date(patient_info_lines, log)
132
+ manifest["reportDate"] = transform_date(report_date)
133
+ manifest["indexedDate"] = manifest["reportDate"]
136
134
 
135
+ for line in patient_info_lines:
137
136
  if "collDate" not in manifest and "Collected" in line:
138
137
  collArray = line.split(" ")
139
138
  coll_date = search_and_grab(collArray, "Collected", 1)
@@ -178,10 +177,11 @@ def process_manifest(
178
177
  include_structural: bool,
179
178
  somatic_translocations: list[str],
180
179
  hyperdiploidy_chromosomes: list[str] | None,
180
+ log: Logger,
181
181
  ):
182
182
  test_text = extract_xml_text(xml_in_file)
183
183
  interpretation_text = extract_interpretation_text(xml_in_file)
184
- manifest = extract_test_data(test_text, interpretation_text)
184
+ manifest = extract_test_data(test_text, interpretation_text, log)
185
185
  manifest.update(extract_patient_data(test_text))
186
186
 
187
187
  file_prefix = f".lifeomic/nextgen/{case_id}/{case_id}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: phc-ingestion
3
- Version: 0.8.36
3
+ Version: 0.8.38
4
4
  Summary: Functions for LifeOmic PHC genomic ingestions
5
5
  License: MIT
6
6
  Author-email: LifeOmic Development <development@lifeomic.com>
@@ -8,11 +8,12 @@ Requires-Python: >=3.11
8
8
  Requires-Dist: jsonschema<5.0.0,>=4.16.0
9
9
  Requires-Dist: lifeomic-logging<0.4.0,>=0.3.2
10
10
  Requires-Dist: natsort==7.1.1
11
+ Requires-Dist: numpy>=2.1.2
11
12
  Requires-Dist: packaging>=23.1
12
- Requires-Dist: pandas<1.6.0,>=1.5.0
13
+ Requires-Dist: pandas>=2.2.3
13
14
  Requires-Dist: ruamel.yaml==0.17.21
14
15
  Requires-Dist: schema>=0.7.5
15
- Requires-Dist: xmltodict==0.13.0
16
+ Requires-Dist: xmltodict>=0.14.2
16
17
  Description-Content-Type: text/markdown
17
18
 
18
19
  # phc-ingestion
@@ -8,15 +8,15 @@ ingestion/caris/util/ga4gh.py,sha256=-jNQj79zspxG67MxHzOfwAhLbb9je55M1h4-i5ri-tU
8
8
  ingestion/caris/util/hla.py,sha256=X_t6ngBRvmdG3m4I2_KnPFeWn3BaH-3IWHtOvDbS32A,770
9
9
  ingestion/caris/util/ihc.py,sha256=vegxudxHj7tLihrXGbEx_ptwkSsu3YCCB1nZVwoiYXg,12312
10
10
  ingestion/caris/util/interpretation.py,sha256=CghNurqeVA5VTBBorU8-ZTN-PVNPnR8wrmTwKCH3568,555
11
- ingestion/caris/util/json.py,sha256=xVEfwKOwyRCEXZWKr9zXooPIrxGkUtSxcAYuDUHkAxw,4706
12
- ingestion/caris/util/metadata.py,sha256=629-yPh_qIZKJCJzJHEvo6EUGxIBW3Gw31RSytF4v08,9541
11
+ ingestion/caris/util/json.py,sha256=HBU3Tf-XSi9fGHANYUtD8maXNYqmmnpncGh0KCDaPEU,5018
12
+ ingestion/caris/util/metadata.py,sha256=C50e5a6zqYeUG_RcZvFvN-UEXWNJb0q03dMOGWkDgO0,10070
13
13
  ingestion/caris/util/specimen_details.py,sha256=R3uKHlLR056XcQbUPI6IO2dLr-z5Z5AJi866DJ379Qw,2105
14
14
  ingestion/caris/util/structural.py,sha256=EUcMIea_WnafoVmFLIyEqlJ_HtYIj_g6qkekXa7QNQs,4628
15
15
  ingestion/caris/util/tar.py,sha256=BGR_2vBbxyMgF-GzJ3SrihsPdOzII4SFVz9tvKV5vo0,482
16
16
  ingestion/caris/util/tests.py,sha256=mcG3A8TW81_sn2Bfoa-Gd6Q1sR3_R4FX2BNskD4DkJk,372
17
17
  ingestion/caris/util/tmb.py,sha256=DVi1wPSjVr_32ZCc6Yb51tGqUlcxUx40yCvuqvNuDx4,1027
18
18
  ingestion/caris/util/tsv.py,sha256=xeIfDUtqG_5ewkbaPLakqm4kQlu6ClgkAf4tefqKlJA,1595
19
- ingestion/caris/util/vcf.py,sha256=btqaWhIXjjpm7uoXeLuAMnxVO7tlwjuia4m9ZbtdHMQ,3440
19
+ ingestion/caris/util/vcf.py,sha256=Lkr4HnjMmMvEBVkD-9EkxRI3HpFmgCkgj6CXN4lBfIg,5058
20
20
  ingestion/foundation/__init__.py,sha256=CuUMsxSvWPAVzvnxx4hois632HpXwhwpjtMtiM98UoM,49
21
21
  ingestion/foundation/process.py,sha256=T8YTvXRiThqE1LTERhrzvvD69mP4qJ7soJ1ZIbu8Y1Y,3151
22
22
  ingestion/foundation/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -24,19 +24,19 @@ ingestion/foundation/util/cnv.py,sha256=YSKCaOBhjZDNXth_GxC-50crDURpTNCMefoHo0uO
24
24
  ingestion/foundation/util/fnv.py,sha256=-VstGsBKXM0duC-IpwUkektoTZ9yQUR0IQcDb1HibY0,5937
25
25
  ingestion/foundation/util/ga4gh.py,sha256=nc14JStpT7tG7v-dXTrbpPZi29I-HbKKBGNxEZAudhg,10987
26
26
  ingestion/foundation/util/interpretation.py,sha256=LVVUmMyD6Un1rIKXqiyQDUC6oIJUd8cU3I9YHD5fsXg,405
27
- ingestion/foundation/util/vcf_etl.py,sha256=vljaXq8KIjp6oYqXq1FyP3bDhHM8THLCDFGGFp9igv0,2163
27
+ ingestion/foundation/util/vcf_etl.py,sha256=ZBrX1XGRz-ymLUEiVcjjqmPZPb-AfD9On8UkZJDa1Dk,2133
28
28
  ingestion/generic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
29
  ingestion/generic/process.py,sha256=WJHV_-SKhrDZ3JS3fm9DVMoW3Zs2t50GiraSV3vlLHE,1548
30
30
  ingestion/generic/utils.py,sha256=1MEIru7uq38IjUdL8lcHqDH0oTki9uWrz1f2e-pmRoU,2814
31
31
  ingestion/nextgen/__init__.py,sha256=7LQ-h_Bvc5P1QcHMdzsqi1Qm4fTJn04-ozar2ty9wSc,59
32
- ingestion/nextgen/process.py,sha256=kDCnU685v7aqJ3i4HpFdb7HqgHRSBKqtYPpuyN7qWmM,3976
32
+ ingestion/nextgen/process.py,sha256=5Z0RfclwTAYZruGDiLPutjPCYFh1DJpoWY9dnttghT4,3993
33
33
  ingestion/nextgen/util/alteration_table.py,sha256=bvTrXEhIRye5BzSXjZEBy1AvXLZgG0rNkNt3e3rcvv0,6127
34
34
  ingestion/nextgen/util/interpretation.py,sha256=56wVk9j-w59gM-11iODXbKuUtZcEwe8zJQSXpjyCguw,872
35
- ingestion/nextgen/util/manifest_helpers.py,sha256=2xrpEtHbCb1Kea1wJeObkDfTiBklmffQt_o2hMgOSOE,1208
35
+ ingestion/nextgen/util/manifest_helpers.py,sha256=LH5em0xsu9Hrs175vfx6SX8W1Ww2FFRp2wIBSfIEMUM,2725
36
36
  ingestion/nextgen/util/nextgen_specific_genes.py,sha256=1jFcqvtYAlJ7eBwOBm1UC2TzAbjHjdlvPBUzxr1G8dY,1206
37
37
  ingestion/nextgen/util/pre_filter_somatic_vcf.py,sha256=mIaUihmGLbS38D4Gy_Qtf1lFAfW0A-LgAgQmsrEiI-M,3529
38
38
  ingestion/nextgen/util/process_cnv.py,sha256=MIirc8e0k6lsaTZkRM3U3L3IvbrcHmKQ4xlIu585514,2430
39
- ingestion/nextgen/util/process_manifest.py,sha256=EnV9I90vnanDvuoErbMfz6yAfjzM5LdhhUF4q5DJd8w,8428
39
+ ingestion/nextgen/util/process_manifest.py,sha256=RsHzDGL1OBea2raoEHACo8owRodIFpX3xVE-aFOoyrg,8428
40
40
  ingestion/nextgen/util/process_structural.py,sha256=FKjkK7BkIlocnLs8rFCjrMC39FCQnD0nQCeWvi7cRoA,7539
41
41
  ingestion/nextgen/util/process_vcf.py,sha256=ZZURSMnZhHDpFahzijZ4MvCfSWTPdIktzmnCKVVUbGs,7768
42
42
  ingestion/nextgen/util/types.py,sha256=SSzt5gv-kss1PR45eQUelypWrGI-dAfQMO3GSD-T-Wg,22
@@ -54,6 +54,6 @@ ingestion/vcf_standardization/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQe
54
54
  ingestion/vcf_standardization/util/af_helpers.py,sha256=dpTzoeIQVeBRt0ETF3a9rp5ojZqznHg4x_hCZ8OPcOg,1061
55
55
  ingestion/vcf_standardization/util/dp_helpers.py,sha256=Nq8oLOLObu4_pv16qwwgpALRlUoJVCULrd9cFOD-eoI,823
56
56
  ingestion/vcf_standardization/util/read_write.py,sha256=x3Pf6Dq8tmolblbCS5CrNmrcHS3FGfqBSFpFgvFGC4g,2526
57
- phc_ingestion-0.8.36.dist-info/WHEEL,sha256=B19PGBCYhWaz2p_UjAoRVh767nYQfk14Sn4TpIZ-nfU,87
58
- phc_ingestion-0.8.36.dist-info/METADATA,sha256=Fe445KhsY-t71Eb7bJ67HuJB96Tl-8lvsS16x8UEJaw,552
59
- phc_ingestion-0.8.36.dist-info/RECORD,,
57
+ phc_ingestion-0.8.38.dist-info/WHEEL,sha256=B19PGBCYhWaz2p_UjAoRVh767nYQfk14Sn4TpIZ-nfU,87
58
+ phc_ingestion-0.8.38.dist-info/METADATA,sha256=zGjelnmcC8NGRJa7qsZmKB6ifrwH1v6PjlR3RDwZ9vs,573
59
+ phc_ingestion-0.8.38.dist-info/RECORD,,