diff --git a/Snakefile b/Snakefile index 0d76a0b2..475f39b6 100644 --- a/Snakefile +++ b/Snakefile @@ -1,7 +1,7 @@ import os from textwrap import dedent -CONTAINER = 'docker://bcgsc/pori-graphkb-loader:v6.2.0' +CONTAINER = 'docker://bcgsc/pori-graphkb-loader:v6.4.0' DATA_DIR = 'snakemake_data' LOGS_DIR = 'snakemake_logs' @@ -26,7 +26,6 @@ COSMIC_EMAIL = config.get('cosmic_email') COSMIC_PASSWORD = config.get('cosmic_password') USE_COSMIC = COSMIC_EMAIL or COSMIC_PASSWORD BACKFILL_TRIALS = config.get('trials') -USE_FDA_UNII = config.get('fda') # due to the non-scriptable download, making FDA optional GITHUB_DATA = 'https://raw.githubusercontent.com/bcgsc/pori_graphkb_loader/develop/data' @@ -34,13 +33,14 @@ rule all: input: f'{DATA_DIR}/civic.COMPLETE', f'{DATA_DIR}/cgi.COMPLETE', f'{DATA_DIR}/docm.COMPLETE', + f'{DATA_DIR}/dgidb.COMPLETE', f'{DATA_DIR}/PMC4468049.COMPLETE', f'{DATA_DIR}/PMC4232638.COMPLETE', f'{DATA_DIR}/uberon.COMPLETE', f'{DATA_DIR}/fdaApprovals.COMPLETE', f'{DATA_DIR}/cancerhotspots.COMPLETE', f'{DATA_DIR}/moa.COMPLETE', - *([f'{DATA_DIR}/ncitFdaXref.COMPLETE'] if USE_FDA_UNII else []), + f'{DATA_DIR}/ncitFdaXref.COMPLETE', *([f'{DATA_DIR}/clinicaltrialsgov.COMPLETE'] if BACKFILL_TRIALS else []), *([f'{DATA_DIR}/cosmic_resistance.COMPLETE', f'{DATA_DIR}/cosmic_fusions.COMPLETE'] if USE_COSMIC else []) @@ -55,34 +55,32 @@ rule download_ncit: rm -rf __MACOSX''') -if USE_FDA_UNII: - rule download_ncit_fda: - output: f'{DATA_DIR}/ncit/FDA-UNII_NCIt_Subsets.txt' - shell: dedent(f'''\ - cd {DATA_DIR}/ncit - wget https://evs.nci.nih.gov/ftp1/FDA/UNII/FDA-UNII_NCIt_Subsets.txt''') +rule download_ncit_fda: + output: f'{DATA_DIR}/ncit/FDA-UNII_NCIt_Subsets.txt' + shell: dedent(f'''\ + cd {DATA_DIR}/ncit + wget https://evs.nci.nih.gov/ftp1/FDA/UNII/FDA-UNII_NCIt_Subsets.txt''') rule download_ensembl: output: f'{DATA_DIR}/ensembl/biomart_export.tsv' shell: dedent(f'''\ cd {DATA_DIR}/ensembl - query_string='' + query_string='' wget -O biomart_export.tsv "http://www.ensembl.org/biomart/martservice?query=$query_string" ''') -if USE_FDA_UNII: - rule download_fda_srs: - output: f'{DATA_DIR}/fda/UNII_Records.txt' - shell: dedent(f'''\ - cd {DATA_DIR}/fda - wget https://fdasis.nlm.nih.gov/srs/download/srs/UNII_Data.zip - unzip UNII_Data.zip - rm UNII_Data.zip +rule download_fda_srs: + output: f'{DATA_DIR}/fda/UNII_Records.txt' + shell: dedent(f'''\ + cd {DATA_DIR}/fda + wget https://precision.fda.gov/uniisearch/archive/latest/UNII_Data.zip + unzip UNII_Data.zip + rm UNII_Data.zip - mv UNII*.txt UNII_Records.txt - ''') + mv UNII*.txt UNII_Records.txt + ''') rule download_refseq: @@ -146,7 +144,7 @@ rule download_cgi: output: f'{DATA_DIR}/cgi/cgi_biomarkers_per_variant.tsv' shell: dedent(f'''\ cd {DATA_DIR}/cgi - wget https://www.cancergenomeinterpreter.org/data/cgi_biomarkers_20180117.zip + wget https://www.cancergenomeinterpreter.org/data/biomarkers/cgi_biomarkers_20180117.zip unzip cgi_biomarkers_20180117.zip ''') @@ -162,19 +160,13 @@ rule download_local_data: rule download_cancerhotspots: output: f'{DATA_DIR}/cancerhotspots/cancerhotspots.v2.maf' shell: dedent(f'''\ + mkdir -p {DATA_DIR}/cancerhotspots cd {DATA_DIR}/cancerhotspots - wget http://download.cbioportal.org/cancerhotspots/cancerhotspots.v2.maf.gz + wget https://cbioportal-download.s3.amazonaws.com/cancerhotspots.v2.maf.gz gunzip cancerhotspots.v2.maf.gz ''') -rule download_clinicaltrialsgov: - output: directory(f'{DATA_DIR}/clinicaltrialsgov') - shell: dedent(f'''\ - cd {DATA_DIR}/clinicaltrialsgov - wget https://clinicaltrials.gov/AllPublicXML.zip - unzip AllPublicXML.zip''') - rule download_cosmic_resistance: output: f'{DATA_DIR}/cosmic/CosmicResistanceMutations.tsv' @@ -228,24 +220,24 @@ rule load_ncit: shell: LOADER_COMMAND + ' file ncit {input.data} &> {log}; cp {log} {output}' -if USE_FDA_UNII: - rule load_fda_srs: - input: expand(rules.load_local.output, local=['vocab']), - data=f'{DATA_DIR}/fda/UNII_Records.txt' - container: CONTAINER - log: f'{LOGS_DIR}/fdaSrs.logs.txt' - output: f'{DATA_DIR}/fdaSrs.COMPLETE' - shell: LOADER_COMMAND + ' file fdaSrs {input.data} &> {log}; cp {log} {output}' +rule load_fda_srs: + input: expand(rules.load_local.output, local=['vocab']), + f'{DATA_DIR}/ncit.COMPLETE', + data=f'{DATA_DIR}/fda/UNII_Records.txt' + container: CONTAINER + log: f'{LOGS_DIR}/fdaSrs.logs.txt' + output: f'{DATA_DIR}/fdaSrs.COMPLETE' + shell: LOADER_COMMAND + ' file fdaSrs {input.data} &> {log}; cp {log} {output}' - rule load_ncit_fda: - input: rules.load_ncit.output, - rules.load_fda_srs.output, - data=rules.download_ncit_fda.output - container: CONTAINER - log: f'{LOGS_DIR}/ncitFdaXref.logs.txt' - output: f'{DATA_DIR}/ncitFdaXref.COMPLETE' - shell: LOADER_COMMAND + ' file ncitFdaXref {input.data} &> {log}; cp {log} {output}' +rule load_ncit_fda: + input: rules.load_ncit.output, + rules.load_fda_srs.output, + data=rules.download_ncit_fda.output + container: CONTAINER + log: f'{LOGS_DIR}/ncitFdaXref.logs.txt' + output: f'{DATA_DIR}/ncitFdaXref.COMPLETE' + shell: LOADER_COMMAND + ' file ncitFdaXref {input.data} &> {log}; cp {log} {output}' rule load_refseq: @@ -285,7 +277,7 @@ rule load_uberon: rule load_drugbank: - input: rules.load_fda_srs.output if USE_FDA_UNII else [], + input: rules.load_fda_srs.output, data=rules.download_drugbank.output container: CONTAINER log: f'{LOGS_DIR}/drugbank.logs.txt' @@ -301,18 +293,9 @@ rule load_oncotree: shell: LOADER_COMMAND + ' api oncotree &> {log}; cp {log} {output}' -rule load_dgidb: - input: rules.load_local.output - container: CONTAINER - log: f'{LOGS_DIR}/dgidb.logs.txt' - output: f'{DATA_DIR}/dgidb.COMPLETE' - shell: LOADER_COMMAND + ' api dgidb &> {log}; cp {log} {output}' - - def get_drug_inputs(wildcards): inputs = [*rules.load_ncit.output] - if USE_FDA_UNII: - inputs.extend(rules.load_fda_srs.output) + inputs.extend(rules.load_fda_srs.output) container: CONTAINER if USE_DRUGBANK: inputs.append(*rules.load_drugbank.output) @@ -322,7 +305,7 @@ def get_drug_inputs(wildcards): rule all_drugs: input: lambda wildcards: get_drug_inputs(wildcards) container: CONTAINER - output: f'{LOGS_DIR}/all_drugs.COMPLETE' + output: f'{DATA_DIR}/all_drugs.COMPLETE' shell: 'touch {output}' @@ -331,10 +314,26 @@ rule all_diseases: rules.load_ncit.output, rules.load_oncotree.output container: CONTAINER - output: f'{LOGS_DIR}/all_diseases.COMPLETE' + output: f'{DATA_DIR}/all_diseases.COMPLETE' + shell: 'touch {output}' + + +rule all_local: + input: expand(rules.load_local.output, local=['vocab', 'signatures', 'chromosomes', 'evidenceLevels', 'aacr', 'asco']), + container: CONTAINER + log: f'{LOGS_DIR}/all_local.logs.txt' + output: f'{DATA_DIR}/all_local.COMPLETE' shell: 'touch {output}' +rule load_dgidb: + input: rules.all_local.output + container: CONTAINER + log: f'{LOGS_DIR}/dgidb.logs.txt' + output: f'{DATA_DIR}/dgidb.COMPLETE' + shell: LOADER_COMMAND + ' api dgidb &> {log}; cp {log} {output}' + + rule load_cancerhotspots: input: expand(rules.load_local.output, local=['vocab', 'signatures', 'chromosomes']), rules.load_oncotree.output, @@ -372,7 +371,7 @@ rule load_civic: container: CONTAINER log: f'{LOGS_DIR}/civic.logs.txt' output: f'{DATA_DIR}/civic.COMPLETE' - shell: LOADER_COMMAND + ' api civic &> {log}; cp {log} {output}' + shell: LOADER_COMMAND + ' civic &> {log}; cp {log} {output}' rule load_cgi: @@ -397,6 +396,7 @@ rule load_docm: rule load_approvals: + input: container: CONTAINER log: f'{LOGS_DIR}/fdaApprovals.logs.txt' output: f'{DATA_DIR}/fdaApprovals.COMPLETE' @@ -406,12 +406,11 @@ rule load_approvals: rule load_clinicaltrialsgov: input: expand(rules.load_local.output, local=['vocab']), rules.all_diseases.output, - rules.all_drugs.output, - data=rules.download_clinicaltrialsgov.output + rules.all_drugs.output container: CONTAINER log: f'{LOGS_DIR}/clinicaltrialsgov.logs.txt' output: f'{DATA_DIR}/clinicaltrialsgov.COMPLETE' - shell: LOADER_COMMAND + ' api clinicaltrialsgov &> {log}; cp {log} {output}' + shell: LOADER_COMMAND + ' clinicaltrialsgov &> {log}; cp {log} {output}' rule load_cosmic_resistance: @@ -443,3 +442,37 @@ rule load_moa: log: f'{LOGS_DIR}/load_moa.logs.txt' output: f'{DATA_DIR}/moa.COMPLETE' shell: LOADER_COMMAND + ' api moa &> {log}; cp {log} {output}' + + +# input isn't actually needed but it is a file-type loader, so a dummy file must be supplied +rule download_sources: + output: f'{DATA_DIR}/local/sources.json' + shell: dedent(f'''\ + cd {DATA_DIR}/local + touch sources.json + ''') + +rule load_sources: + input: f'{DATA_DIR}/local/sources.json' + container: CONTAINER + log: f'{LOGS_DIR}/sources.logs.txt' + output: f'{DATA_DIR}/sources.COMPLETE' + shell: LOADER_COMMAND + ' file sources {input} &> {log}; cp {log} {output}' + + +rule all_ontologies: + input: expand(rules.load_local.output, local=['vocab', 'signatures', 'chromosomes', 'evidenceLevels', 'aacr', 'asco']), + rules.load_oncotree.output, + rules.load_ensembl.output, + rules.all_drugs.output, + rules.all_diseases.output, + rules.load_uberon.output, + rules.load_approvals.output, + rules.load_ncit.output, + rules.load_sources.output, + rules.load_fda_srs.output, + rules.load_ncit_fda.output, + rules.load_dgidb.output + container: CONTAINER + output: f'{DATA_DIR}/all_ontologies.COMPLETE' + shell: 'touch {output}' diff --git a/bin/load.js b/bin/load.js index e7a5b160..166ef615 100644 --- a/bin/load.js +++ b/bin/load.js @@ -1,6 +1,3 @@ -const fs = require('fs'); -const path = require('path'); - const { runLoader } = require('../src'); const { createOptionsMenu, fileExists } = require('../src/cli'); @@ -24,6 +21,7 @@ const ontology = require('../src/ontology'); const refseq = require('../src/refseq'); const PMC4468049 = require('../src/PMC4468049'); const PMC4232638 = require('../src/PMC4232638'); +const sources = require('../src/all_sources'); const uberon = require('../src/uberon'); const variants = require('../src/variants'); const asco = require('../src/asco'); @@ -35,7 +33,12 @@ const cosmicResistance = require('../src/cosmic/resistance'); const cosmicFusions = require('../src/cosmic/fusions'); const API_MODULES = { - asco, clinicaltrialsgov, dgidb, docm, fdaApprovals, moa, oncotree, + asco, + dgidb, + docm, + fdaApprovals, + moa, + oncotree, }; const FILE_MODULES = { @@ -44,7 +47,6 @@ const FILE_MODULES = { cancerhotspots, cgi, cgl, - clinicaltrialsgov, diseaseOntology, drugbank, ensembl, @@ -54,6 +56,7 @@ const FILE_MODULES = { ncitFdaXref, ontology, refseq, + sources, uberon, variants, }; @@ -68,11 +71,12 @@ const ALL_MODULES = { ...FILE_MODULES, ...COSMIC_MODULES, civic, + clinicaltrialsgov, }; const parser = createOptionsMenu(); -const subparsers = parser.add_subparsers({ help: 'Sub-command help', required: true }); +const subparsers = parser.add_subparsers({ dest: 'subparser_name', help: 'Sub-command help', required: true }); const apiParser = subparsers.add_parser('api'); apiParser.add_argument('module', { choices: Object.keys(API_MODULES), @@ -100,7 +104,17 @@ civicParser.add_argument('--trustedCurators', { help: 'CIViC User IDs of curators whose statements should be imported even if they have not yet been reviewed (evidence is submitted but not accepted)', nargs: '+', }); +civicParser.add_argument('--noUpdate', { + action: 'store_true', + default: false, + help: 'Will not check for updating content of existing GraphKB Statements', +}); +const clinicaltrialsgovParser = subparsers.add_parser('clinicaltrialsgov'); +clinicaltrialsgovParser.add_argument('--days', { + help: 'Load new and existing studies added or modified (last update posted) in the last # of days', + type: Number, +}); const cosmicParser = subparsers.add_parser('cosmic'); cosmicParser.add_argument('module', { @@ -116,34 +130,22 @@ cosmicParser.add_argument('classification', { type: fileExists, }); -const { module: moduleName, input, ...options } = parser.parse_args(); +const { + subparser_name, module: moduleName, input, ...options +} = parser.parse_args(); let loaderFunction; if (input) { - loaderFunction = ALL_MODULES[moduleName || 'civic'].uploadFile; + loaderFunction = ALL_MODULES[moduleName || subparser_name].uploadFile; } else { - loaderFunction = ALL_MODULES[moduleName || 'civic'].upload; + loaderFunction = ALL_MODULES[moduleName || subparser_name].upload; } const loaderOptions = { ...options }; if (input) { - if (moduleName === 'clinicaltrialsgov') { - if (fs.lstatSync(input).isDirectory()) { - const files = fs.readdirSync(input) - .map(filename => path.join(input, filename)); - loaderOptions.files = files; - } else { - loaderOptions.files = [input]; - } - } else { - loaderOptions.filename = input; - - if (options.module === 'cosmic') { - loaderOptions.classification = options.classification; - } - } + loaderOptions.filename = input; } runLoader(options, loaderFunction, loaderOptions) diff --git a/data/chromosomes.json b/data/chromosomes.json index c8fabdba..3c2046b4 100644 --- a/data/chromosomes.json +++ b/data/chromosomes.json @@ -91,15 +91,18 @@ }, "MT": { "biotype": "chromosome", - "name": "chrMT" + "name": "chrMT", + "displayName":"mt" }, "X": { "biotype": "chromosome", - "name": "chrX" + "name": "chrX", + "displayName":"x" }, "Y": { "biotype": "chromosome", - "name": "chrY" + "name": "chrY", + "displayName":"y" } }, "sources": { diff --git a/data/diseases.json b/data/diseases.json index a6e2ebc5..e696300c 100644 --- a/data/diseases.json +++ b/data/diseases.json @@ -3,7 +3,8 @@ "defaultNameToSourceId": true, "records": { "all solid tumors": { - "name": "all solid tumors" + "name": "all solid tumors", + "source": "oncokb" }, "phc": { "links": [ diff --git a/data/evidenceLevels.json b/data/evidenceLevels.json index e077635c..859df1dd 100644 --- a/data/evidenceLevels.json +++ b/data/evidenceLevels.json @@ -634,6 +634,14 @@ "class": "CrossReferenceOf", "source": "ipr", "target": "profyle-t1" + }, + { + "class": "CrossReferenceOf", + "target": "profyle-t1a" + }, + { + "class": "CrossReferenceOf", + "target": "profyle-t1b" } ], "source": "ipr", @@ -712,6 +720,16 @@ "class": "CrossReferenceOf", "source": "ipr", "target": "profyle-t2" + }, + { + "class": "CrossReferenceOf", + "source": "ipr", + "target": "profyle-t2a" + }, + { + "class": "CrossReferenceOf", + "source": "ipr", + "target": "profyle-t2b" } ], "source": "ipr", @@ -785,6 +803,16 @@ "class": "CrossReferenceOf", "source": "ipr", "target": "profyle-t3" + }, + { + "class": "CrossReferenceOf", + "source": "ipr", + "target": "profyle-t3a" + }, + { + "class": "CrossReferenceOf", + "source": "ipr", + "target": "profyle-t3b" } ], "source": "ipr", @@ -858,6 +886,16 @@ "class": "CrossReferenceOf", "source": "ipr", "target": "profyle-t4" + }, + { + "class": "CrossReferenceOf", + "source": "ipr", + "target": "profyle-t4a" + }, + { + "class": "CrossReferenceOf", + "source": "ipr", + "target": "profyle-t4b" } ], "source": "ipr", @@ -901,6 +939,16 @@ "class": "CrossReferenceOf", "source": "ipr", "target": "profyle-t5" + }, + { + "class": "CrossReferenceOf", + "source": "ipr", + "target": "profyle-t5a" + }, + { + "class": "CrossReferenceOf", + "source": "ipr", + "target": "profyle-t5b" } ], "source": "ipr", @@ -1091,37 +1139,167 @@ "profyle-t1": { "description": "Biomarkers that predict response or resistance to FDA/HC-approved therapies", "displayName": "PROFYLE T1", + "links": [ + { + "class": "CrossReferenceOf", + "target": "profyle-t1a" + }, + { + "class": "CrossReferenceOf", + "target": "profyle-t1b" + } + ], "name": "PROFYLE Therapeutic Level 1", "source": "profyle", "sourceId": "T1" }, + "profyle-t1a": { + "comment": "Not intended to be used in Statement", + "description": "Biomarkers that predict response or resistance to FDA/HC-approved therapies in the same tumour type", + "displayName": "PROFYLE T1A", + "name": "PROFYLE Therapeutic Level 1A", + "source": "profyle", + "sourceId": "T1A" + }, + "profyle-t1b": { + "comment": "Not intended to be used in Statement", + "description": "Biomarkers that predict response or resistance to FDA/HC-approved therapies in another tumour type", + "displayName": "PROFYLE T1B", + "name": "PROFYLE Therapeutic Level 1B", + "source": "profyle", + "sourceId": "T1B" + }, "profyle-t2": { "description": "Biomarkers that predict response or resistance to therapies based on well-powered clinical trials", "displayName": "PROFYLE T2", + "links": [ + { + "class": "CrossReferenceOf", + "target": "profyle-t2a" + }, + { + "class": "CrossReferenceOf", + "target": "profyle-t2b" + } + ], "name": "PROFYLE Therapeutic Level 2", "source": "profyle", "sourceId": "T2" }, + "profyle-t2a": { + "comment": "Not intended to be used in Statement", + "description": "Biomarkers that predict response or resistance to therapies based on well-powered clinical trials in the same tumour type", + "displayName": "PROFYLE T2A", + "name": "PROFYLE Therapeutic Level 2A", + "source": "profyle", + "sourceId": "T2A" + }, + "profyle-t2b": { + "comment": "Not intended to be used in Statement", + "description": "Biomarkers that predict response or resistance to therapies based on well-powered clinical trials in another tumour type", + "displayName": "PROFYLE T2B", + "name": "PROFYLE Therapeutic Level 2B", + "source": "profyle", + "sourceId": "T2B" + }, "profyle-t3": { "description": "Biomarkers that predict response or resistance to therapies based on small series, case-reports, or that serve as inclusion criteria for a clinical trial", "displayName": "PROFYLE T3", + "links": [ + { + "class": "CrossReferenceOf", + "target": "profyle-t3a" + }, + { + "class": "CrossReferenceOf", + "target": "profyle-t3b" + } + ], "name": "PROFYLE Therapeutic Level 3", "source": "profyle", "sourceId": "T3" }, + "profyle-t3a": { + "comment": "Not intended to be used in Statement", + "description": "Biomarkers that predict response or resistance to therapies based on small series, case-reports, or that serve as inclusion criteria for a clinical trial, in the same tumour type", + "displayName": "PROFYLE T3A", + "name": "PROFYLE Therapeutic Level 3A", + "source": "profyle", + "sourceId": "T3A" + }, + "profyle-t3b": { + "comment": "Not intended to be used in Statement", + "description": "Biomarkers that predict response or resistance to therapies based on small series, case-reports, or that serve as inclusion criteria for a clinical trial, in another tumour type", + "displayName": "PROFYLE T3B", + "name": "PROFYLE Therapeutic Level 3B", + "source": "profyle", + "sourceId": "T3B" + }, "profyle-t4": { "description": "Biomarkers that show plausible therapeutic significance based on preclinical studies", "displayName": "PROFYLE T4", + "links": [ + { + "class": "CrossReferenceOf", + "target": "profyle-t4a" + }, + { + "class": "CrossReferenceOf", + "target": "profyle-t4b" + } + ], "name": "PROFYLE Therapeutic Level 4", "source": "profyle", "sourceId": "T4" }, + "profyle-t4a": { + "comment": "Not intended to be used in Statement", + "description": "Biomarkers that show plausible therapeutic significance based on preclinical studies in the same tumour type", + "displayName": "PROFYLE T4A", + "name": "PROFYLE Therapeutic Level 4A", + "source": "profyle", + "sourceId": "T4A" + }, + "profyle-t4b": { + "comment": "Not intended to be used in Statement", + "description": "Biomarkers that show plausible therapeutic significance based on preclinical studies in another tumour type", + "displayName": "PROFYLE T4B", + "name": "PROFYLE Therapeutic Level 4B", + "source": "profyle", + "sourceId": "T4B" + }, "profyle-t5": { "description": "Novel biomarkers predicted to be oncogenic in therapeutically actionable genes, or evidence of activation of a therapeutically actionable pathway", "displayName": "PROFYLE T5", + "links": [ + { + "class": "CrossReferenceOf", + "target": "profyle-t5a" + }, + { + "class": "CrossReferenceOf", + "target": "profyle-t5b" + } + ], "name": "PROFYLE Therapeutic Level 5", "source": "profyle", "sourceId": "T5" + }, + "profyle-t5a": { + "comment": "Not intended to be used in Statement", + "description": "Novel biomarkers predicted to be oncogenic in therapeutically actionable genes, or evidence of activation of a therapeutically actionable pathway in the same tumour type", + "displayName": "PROFYLE T5A", + "name": "PROFYLE Therapeutic Level 5A", + "source": "profyle", + "sourceId": "T5A" + }, + "profyle-t5b": { + "comment": "Not intended to be used in Statement", + "description": "Novel biomarkers predicted to be oncogenic in therapeutically actionable genes, or evidence of activation of a therapeutically actionable pathway in another tumour type", + "displayName": "PROFYLE T5B", + "name": "PROFYLE Therapeutic Level 5B", + "source": "profyle", + "sourceId": "T5B" } }, "sources": { diff --git a/data/vocab.json b/data/vocab.json index 68cf370c..22ce6f2c 100644 --- a/data/vocab.json +++ b/data/vocab.json @@ -135,6 +135,15 @@ "biological": { "name": "biological" }, + "cancer gene": { + "description": "Tumourigenesis gene that is neither oncogenic nor tumour suppressive", + "links": [ + { + "class": "SubClassOf", + "target": "tumourigenesis" + } + ] + }, "conditional loss of function": { "description": "loss-of-function is predicted or assumed (by the literature) under specific conditions only", "links": [ @@ -213,7 +222,7 @@ "links": [ { "class": "SubClassOf", - "target": "structural variant" + "target": "mutation" } ] }, @@ -979,6 +988,14 @@ } ] }, + "likely protective": { + "links": [ + { + "class": "SubClassOf", + "target": "protective" + } + ] + }, "likely resistance": { "links": [ { @@ -1243,6 +1260,19 @@ } ] }, + "no neomorphic": { + "deprecated": true, + "links": [ + { + "class": "AliasOf", + "target": "no switch of function" + }, + { + "class": "OppositeOf", + "target": "neomorphic" + } + ] + }, "no protein expression": { "links": [ { @@ -2121,7 +2151,7 @@ } ] }, - "unaltered function": { + "unaltered function": { "description": "event is not associated with a change in protein function", "links": [ { @@ -2129,7 +2159,7 @@ "target": "no functional effect" } ] - }, + }, "underexpression": { "deprecated": true, "links": [ @@ -2220,12 +2250,7 @@ ] }, "wild type": { - "links": [ - { - "class": "SubClassOf", - "target": "no functional effect" - } - ] + "comment": "Not a SubClassOf 'no functional effect' anymore. See KBDEV-1231" }, "wildtype": { "deprecated": true, @@ -2256,4 +2281,4 @@ "name": "moa" } } -} +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 6a2881a7..674875bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bcgsc-pori/graphkb-loader", - "version": "6.3.2", + "version": "8.0.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@bcgsc-pori/graphkb-loader", - "version": "6.3.2", + "version": "8.0.2", "license": "GPL-3", "dependencies": { "@bcgsc-pori/graphkb-parser": "^1.1.1", @@ -50,6 +50,19 @@ "jest-junit": "^13.0.0" } }, + "node_modules/@babel/code-frame": { + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/compat-data": { "version": "7.16.4", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.4.tgz", @@ -89,36 +102,10 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/core/node_modules/@babel/code-frame": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", - "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core/node_modules/@babel/highlight": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", - "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.15.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -134,28 +121,20 @@ } }, "node_modules/@babel/generator": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.0.tgz", - "integrity": "sha512-RR8hUCfRQn9j9RPKEVXo9LiwoxLPYn6hNZlvUOR8tSnaxlD0p0+la00ZP9/SnRt6HchKr+X0fO2r8vrETiJGew==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "dependencies": { - "@babel/types": "^7.16.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "@babel/types": "^7.23.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/generator/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@babel/helper-compilation-targets": { "version": "7.16.3", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.3.tgz", @@ -175,47 +154,43 @@ } }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" } }, - "node_modules/@babel/helper-function-name": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz", - "integrity": "sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog==", + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, - "dependencies": { - "@babel/helper-get-function-arity": "^7.16.0", - "@babel/template": "^7.16.0", - "@babel/types": "^7.16.0" - }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz", - "integrity": "sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ==", + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/types": "^7.16.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz", - "integrity": "sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "dependencies": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -313,21 +288,30 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz", - "integrity": "sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "dependencies": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" @@ -356,10 +340,24 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/highlight": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/parser": { - "version": "7.16.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.4.tgz", - "integrity": "sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -553,58 +551,33 @@ } }, "node_modules/@babel/template": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz", - "integrity": "sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.16.0", - "@babel/parser": "^7.16.0", - "@babel/types": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template/node_modules/@babel/code-frame": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", - "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "dependencies": { - "@babel/highlight": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template/node_modules/@babel/highlight": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", - "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.15.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.16.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.3.tgz", - "integrity": "sha512-eolumr1vVMjqevCpwVO99yN/LoGL0EyHiLO5I043aYQvwOJ9eR5UsZSClHVCzfhBduMAsSzgA/6AyqPjNayJag==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.16.0", - "@babel/generator": "^7.16.0", - "@babel/helper-function-name": "^7.16.0", - "@babel/helper-hoist-variables": "^7.16.0", - "@babel/helper-split-export-declaration": "^7.16.0", - "@babel/parser": "^7.16.3", - "@babel/types": "^7.16.0", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -612,32 +585,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/@babel/code-frame": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", - "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/@babel/highlight": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", - "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.15.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/traverse/node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -648,12 +595,13 @@ } }, "node_modules/@babel/types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz", - "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.15.7", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -2108,6 +2056,54 @@ "node": ">=8" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", @@ -2276,9 +2272,9 @@ } }, "node_modules/@xmldom/xmldom": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.0.tgz", - "integrity": "sha512-7wVnF+rKrVDEo1xjzkkidTG0grclaVnX0vKa0z9JSXcEdtftUJjvU33jLGg6SHyvs3eeqEsI7jZ6NxYfRypEEg==", + "version": "0.8.6", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.6.tgz", + "integrity": "sha512-uRjjusqpoqfmRkTaNuLJ2VohVr67Q5YwDATW3VU7PfzTj6IRaihGrYI7zckGZjxQPBIp63nfvJbM+Yu5ICh0Bg==", "engines": { "node": ">=10.0.0" } @@ -2667,9 +2663,9 @@ } }, "node_modules/babel-plugin-istanbul/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -2777,12 +2773,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -3698,9 +3694,9 @@ } }, "node_modules/eslint-config-airbnb-base/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -3790,9 +3786,9 @@ "dev": true }, "node_modules/eslint-plugin-jest": { - "version": "22.20.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-22.20.0.tgz", - "integrity": "sha512-UwHGXaYprxwd84Wer8H7jZS+5C3LeEaU8VD7NqORY6NmPJrs+9Ugbq3wyjqO3vWtSsDaLar2sqEB8COmOZA4zw==", + "version": "22.21.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-22.21.0.tgz", + "integrity": "sha512-OaqnSS7uBgcGiqXUiEnjoqxPNKvR4JWG5mSRkzVoR6+vDwlqqp11beeql1hYs0HTbdhiwrxWLxbX0Vx7roG3Ew==", "dev": true, "dependencies": { "@typescript-eslint/experimental-utils": "^1.13.0" @@ -3917,9 +3913,9 @@ } }, "node_modules/eslint-plugin-react/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -4624,9 +4620,9 @@ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -5543,9 +5539,9 @@ } }, "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -7481,46 +7477,6 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/jest-message-util/node_modules/@babel/code-frame": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", - "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/jest-message-util/node_modules/@babel/highlight": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", - "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.15.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/jest-message-util/node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/jest-message-util/node_modules/@jest/types": { "version": "27.2.5", "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.2.5.tgz", @@ -8710,9 +8666,9 @@ "dev": true }, "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -9411,26 +9367,6 @@ "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", "dev": true }, - "node_modules/jsdom/node_modules/psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "node_modules/jsdom/node_modules/tough-cookie": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", - "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", - "dev": true, - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.1.2" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/jsdom/node_modules/tr46": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", @@ -9506,13 +9442,10 @@ "dev": true }, "node_modules/json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, "bin": { "json5": "lib/cli.js" }, @@ -9587,14 +9520,14 @@ } }, "node_modules/jszip": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz", - "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", - "set-immediate-shim": "~1.0.1" + "setimmediate": "^1.0.5" } }, "node_modules/jszip/node_modules/readable-stream": { @@ -9916,9 +9849,9 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -10457,6 +10390,12 @@ "react-is": "^16.13.1" } }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -10475,6 +10414,12 @@ "node": ">=0.4.x" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -10550,9 +10495,9 @@ } }, "node_modules/read-excel-file/node_modules/@xmldom/xmldom": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.5.tgz", - "integrity": "sha512-V3BIhmY36fXZ1OtVcI9W+FxQqxVLsPKcNjWigIaa81dLC9IolJl5Mt4Cvhmr0flUnjSpTdrbMTSbXqYqV5dT6A==", + "version": "0.7.9", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.9.tgz", + "integrity": "sha512-yceMpm/xd4W2a85iqZyO09gTnHvXF6pyiWjD2jcOJs7hRoZtNNOO1eJlhHj1ixA+xip2hOyGn+LgcvLCMo5zXA==", "engines": { "node": ">=10.0.0" } @@ -10622,6 +10567,12 @@ "node": ">=0.10.5" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, "node_modules/resolve": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", @@ -10708,21 +10659,13 @@ } }, "node_modules/semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "bin": { "semver": "bin/semver" } }, - "node_modules/set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -11148,6 +11091,21 @@ "node": ">=8.0" } }, + "node_modules/tough-cookie": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "dev": true, + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/tr46": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", @@ -11183,9 +11141,9 @@ } }, "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "dependencies": { "minimist": "^1.2.0" @@ -11256,9 +11214,9 @@ "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==" }, "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", "dev": true, "engines": { "node": ">= 4.0.0" @@ -11311,6 +11269,16 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -11507,9 +11475,9 @@ } }, "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -11638,9 +11606,9 @@ } }, "node_modules/ws": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", - "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, "engines": { "node": ">=8.3.0" @@ -11702,18 +11670,21 @@ "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" }, "node_modules/xml2js": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", - "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", "dependencies": { "sax": ">=0.6.0", - "xmlbuilder": "~9.0.1" + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" } }, "node_modules/xmlbuilder": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", - "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", "engines": { "node": ">=4.0" } @@ -11828,6 +11799,16 @@ } }, "dependencies": { + "@babel/code-frame": { + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "dev": true, + "requires": { + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" + } + }, "@babel/compat-data": { "version": "7.16.4", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.4.tgz", @@ -11857,30 +11838,10 @@ "source-map": "^0.5.0" }, "dependencies": { - "@babel/code-frame": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", - "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", - "dev": true, - "requires": { - "@babel/highlight": "^7.16.0" - } - }, - "@babel/highlight": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", - "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.15.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true }, "source-map": { @@ -11892,22 +11853,15 @@ } }, "@babel/generator": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.0.tgz", - "integrity": "sha512-RR8hUCfRQn9j9RPKEVXo9LiwoxLPYn6hNZlvUOR8tSnaxlD0p0+la00ZP9/SnRt6HchKr+X0fO2r8vrETiJGew==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "requires": { - "@babel/types": "^7.16.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } + "@babel/types": "^7.23.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" } }, "@babel/helper-compilation-targets": { @@ -11923,40 +11877,36 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } }, - "@babel/helper-function-name": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz", - "integrity": "sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.16.0", - "@babel/template": "^7.16.0", - "@babel/types": "^7.16.0" - } + "@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true }, - "@babel/helper-get-function-arity": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz", - "integrity": "sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ==", + "@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" } }, "@babel/helper-hoist-variables": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz", - "integrity": "sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.22.5" } }, "@babel/helper-member-expression-to-functions": { @@ -12030,18 +11980,24 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz", - "integrity": "sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.22.5" } }, + "@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "dev": true + }, "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true }, "@babel/helper-validator-option": { @@ -12061,10 +12017,21 @@ "@babel/types": "^7.16.0" } }, + "@babel/highlight": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + } + }, "@babel/parser": { - "version": "7.16.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.4.tgz", - "integrity": "sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true }, "@babel/plugin-syntax-async-generators": { @@ -12204,75 +12171,34 @@ } }, "@babel/template": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz", - "integrity": "sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "requires": { - "@babel/code-frame": "^7.16.0", - "@babel/parser": "^7.16.0", - "@babel/types": "^7.16.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", - "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", - "dev": true, - "requires": { - "@babel/highlight": "^7.16.0" - } - }, - "@babel/highlight": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", - "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.15.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - } + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" } }, "@babel/traverse": { - "version": "7.16.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.3.tgz", - "integrity": "sha512-eolumr1vVMjqevCpwVO99yN/LoGL0EyHiLO5I043aYQvwOJ9eR5UsZSClHVCzfhBduMAsSzgA/6AyqPjNayJag==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.0", - "@babel/generator": "^7.16.0", - "@babel/helper-function-name": "^7.16.0", - "@babel/helper-hoist-variables": "^7.16.0", - "@babel/helper-split-export-declaration": "^7.16.0", - "@babel/parser": "^7.16.3", - "@babel/types": "^7.16.0", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, "dependencies": { - "@babel/code-frame": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", - "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", - "dev": true, - "requires": { - "@babel/highlight": "^7.16.0" - } - }, - "@babel/highlight": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", - "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.15.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -12282,12 +12208,13 @@ } }, "@babel/types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz", - "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.15.7", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } }, @@ -13428,6 +13355,45 @@ } } }, + "@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", @@ -13583,9 +13549,9 @@ } }, "@xmldom/xmldom": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.0.tgz", - "integrity": "sha512-7wVnF+rKrVDEo1xjzkkidTG0grclaVnX0vKa0z9JSXcEdtftUJjvU33jLGg6SHyvs3eeqEsI7jZ6NxYfRypEEg==" + "version": "0.8.6", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.6.tgz", + "integrity": "sha512-uRjjusqpoqfmRkTaNuLJ2VohVr67Q5YwDATW3VU7PfzTj6IRaihGrYI7zckGZjxQPBIp63nfvJbM+Yu5ICh0Bg==" }, "abab": { "version": "2.0.5", @@ -13882,9 +13848,9 @@ } }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } @@ -13973,12 +13939,12 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "browser-process-hrtime": { @@ -14848,9 +14814,9 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } @@ -14936,9 +14902,9 @@ } }, "eslint-plugin-jest": { - "version": "22.20.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-22.20.0.tgz", - "integrity": "sha512-UwHGXaYprxwd84Wer8H7jZS+5C3LeEaU8VD7NqORY6NmPJrs+9Ugbq3wyjqO3vWtSsDaLar2sqEB8COmOZA4zw==", + "version": "22.21.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-22.21.0.tgz", + "integrity": "sha512-OaqnSS7uBgcGiqXUiEnjoqxPNKvR4JWG5mSRkzVoR6+vDwlqqp11beeql1hYs0HTbdhiwrxWLxbX0Vx7roG3Ew==", "dev": true, "requires": { "@typescript-eslint/experimental-utils": "^1.13.0" @@ -15021,9 +14987,9 @@ } }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } @@ -15399,9 +15365,9 @@ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -16075,9 +16041,9 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } @@ -17747,39 +17713,6 @@ "stack-utils": "^2.0.3" }, "dependencies": { - "@babel/code-frame": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", - "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", - "dev": true, - "requires": { - "@babel/highlight": "^7.16.0" - } - }, - "@babel/highlight": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", - "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.15.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - } - } - }, "@jest/types": { "version": "27.2.5", "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.2.5.tgz", @@ -18708,9 +18641,9 @@ "dev": true }, "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -19064,23 +18997,6 @@ "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", "dev": true }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "tough-cookie": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", - "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", - "dev": true, - "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.1.2" - } - }, "tr46": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", @@ -19140,13 +19056,10 @@ "dev": true }, "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true }, "jsonify": { "version": "0.0.0", @@ -19202,14 +19115,14 @@ } }, "jszip": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz", - "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", "requires": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", - "set-immediate-shim": "~1.0.1" + "setimmediate": "^1.0.5" }, "dependencies": { "readable-stream": { @@ -19485,9 +19398,9 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } @@ -19908,6 +19821,12 @@ "react-is": "^16.13.1" } }, + "psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -19919,6 +19838,12 @@ "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==", "dev": true }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -19973,9 +19898,9 @@ }, "dependencies": { "@xmldom/xmldom": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.5.tgz", - "integrity": "sha512-V3BIhmY36fXZ1OtVcI9W+FxQqxVLsPKcNjWigIaa81dLC9IolJl5Mt4Cvhmr0flUnjSpTdrbMTSbXqYqV5dT6A==" + "version": "0.7.9", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.9.tgz", + "integrity": "sha512-yceMpm/xd4W2a85iqZyO09gTnHvXF6pyiWjD2jcOJs7hRoZtNNOO1eJlhHj1ixA+xip2hOyGn+LgcvLCMo5zXA==" } } }, @@ -20023,6 +19948,12 @@ "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==", "dev": true }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, "resolve": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", @@ -20088,14 +20019,9 @@ } }, "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" - }, - "set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==" }, "setimmediate": { "version": "1.0.5", @@ -20431,6 +20357,18 @@ "is-number": "^7.0.0" } }, + "tough-cookie": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "dev": true, + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + } + }, "tr46": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", @@ -20463,9 +20401,9 @@ }, "dependencies": { "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "requires": { "minimist": "^1.2.0" @@ -20520,9 +20458,9 @@ "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==" }, "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", "dev": true }, "unzipper": { @@ -20574,6 +20512,16 @@ "punycode": "^2.1.0" } }, + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -20754,9 +20702,9 @@ } }, "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true }, "wordwrap": { @@ -20857,9 +20805,9 @@ } }, "ws": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", - "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, "requires": {} }, @@ -20909,18 +20857,18 @@ } }, "xml2js": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", - "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", "requires": { "sax": ">=0.6.0", - "xmlbuilder": "~9.0.1" + "xmlbuilder": "~11.0.0" } }, "xmlbuilder": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", - "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" }, "xmlchars": { "version": "2.2.0", diff --git a/package.json b/package.json index 438fa291..b4b59389 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@bcgsc-pori/graphkb-loader", "main": "src/index.js", - "version": "6.3.2", + "version": "8.0.2", "repository": { "type": "git", "url": "https://github.com/bcgsc/pori_graphkb_loader.git" @@ -57,7 +57,7 @@ "lint": "eslint -c .eslintrc.json src test", "version": "echo $npm_package_version", "start": "node bin/load.js", - "start:recent-trials": "node bin/load.js api clinicaltrialsgov", + "start:recent-trials": "node bin/load.js clinicaltrialsgov --days 14", "start:fda-approvals": "node bin/load.js api fdaApprovals", "start:oncotree": "node bin/load.js api oncotree", "start:civic": "node bin/load.js civic", diff --git a/src/all_sources/README.md b/src/all_sources/README.md new file mode 100644 index 00000000..c18cb77b --- /dev/null +++ b/src/all_sources/README.md @@ -0,0 +1,8 @@ +# Source Records + +This loader adds all source records in sources.js. Among these, the pubmed source record is required in order for the Pubmed importer to work. +This loader does not require any external data. + +```bash +node bin/load.js api all_sources +``` diff --git a/src/all_sources/index.js b/src/all_sources/index.js new file mode 100644 index 00000000..c866b117 --- /dev/null +++ b/src/all_sources/index.js @@ -0,0 +1,20 @@ +const { logger } = require('../logging'); +const sources = require('../sources'); + +const uploadFile = async ({ conn }) => { + for (const [key, source] of Object.entries(sources)) { + logger.info('Retrieving the record details'); + + try { + const addedSource = await conn.addSource(source); + logger.info(`Source added successfully. Source, sourceID: ${addedSource.displayName},${addedSource['@rid']}`); + } catch (err) { + logger.error(`Error adding source for key ${key}: ${err}`); + } + } +}; + + +module.exports = { + sources, uploadFile, +}; diff --git a/src/cancergenomeinterpreter/README.md b/src/cancergenomeinterpreter/README.md index f33c3810..977c3708 100644 --- a/src/cancergenomeinterpreter/README.md +++ b/src/cancergenomeinterpreter/README.md @@ -8,12 +8,11 @@ files. First, download the data ```bash -wget https://www.cancergenomeinterpreter.org/data/cgi_biomarkers_latest.zip -unzip cgi_biomarkers_latest.zip +wget https://www.cancergenomeinterpreter.org/data/biomarkers/cgi_biomarkers_latest.tsv ``` Then load into graphkb ```bash -node bin/load.js file cgi cgi_biomarkers_per_variant.tsv +node bin/load.js file cgi cgi_biomarkers_latest.tsv ``` diff --git a/src/cancergenomeinterpreter/index.js b/src/cancergenomeinterpreter/index.js index 6542ce4f..2c69dd42 100644 --- a/src/cancergenomeinterpreter/index.js +++ b/src/cancergenomeinterpreter/index.js @@ -5,7 +5,6 @@ const kbParser = require('@bcgsc-pori/graphkb-parser'); const { loadDelimToJson, convertRowFields, - hashRecordToId, } = require('../util'); const { orderPreferredOntologyTerms, rid, @@ -15,7 +14,6 @@ const _trials = require('../clinicaltrialsgov'); const _pubmed = require('../entrez/pubmed'); const _asco = require('../asco'); const _gene = require('../entrez/gene'); -const { uploadFromJSON } = require('../ontology'); const { cgi: SOURCE_DEFN } = require('../sources'); @@ -30,7 +28,6 @@ const HEADER = { evidenceLevel: 'Evidence level', gene: 'Gene', genomic: 'gDNA', - protein: 'individual_mutation', relevance: 'Association', reviewData: 'Curation date', reviewer: 'Curator', @@ -38,23 +35,6 @@ const HEADER = { variantClass: 'Alteration type', }; -const evidenceLevels = { - class: 'EvidenceLevel', - defaultNameToSourceId: true, - records: { - 'CPIC guidelines': {}, - 'Case report': {}, - 'Early trials': {}, - 'European LeukemiaNet guidelines': {}, - 'FDA guidelines': {}, - 'Late trials': {}, - 'NCCN guidelines': {}, - 'NCCN/CAP guidelines': {}, - 'Pre-clinical': {}, - }, - sources: { default: SOURCE_DEFN }, -}; - // mappings are given primarily to fix known typos const relevanceMapping = { 'increased toxicity (myelosupression)': 'increased toxicity (myelosuppression)', @@ -86,6 +66,7 @@ const therapyMapping = { 'jak inhibitors (alone or in combination)': 'jak inhibitor', 'mek inhibitors (alone or in combination)': 'mek inhibitor', tensirolimus: 'temsirolimus', + 'trastuzumab deruxtecan-nxki': 'fam-trastuzumab deruxtecan-nxki', }; @@ -489,26 +470,49 @@ const uploadFile = async ({ const counts = { error: 0, skip: 0, success: 0 }; // tracking errors relative to the number of total statements const inputCounts = { error: 0, skip: 0, success: 0 }; // tracking errors relative to the input number of records - logger.info('creating the evidence levels'); - await uploadFromJSON({ conn, data: evidenceLevels }); - logger.info('preloading the pubmed cache'); + const perVariantRows = []; + + for (let index = 0; index < rows.length; index++) { + let match, + protein; + + if (match = /^(\w+) \(([A-Z0-9*,;]+)\)$/.exec(rows[index].Biomarker)) { + const mutations = match[2].split(','); + + for (let i = 0; i < mutations.length; i++) { + protein = `${match[1]}:${mutations[i]}`; + perVariantRows.push({ + ...rows[i], + protein, + sourceId: `${index + 1}:${i + 1}`, + }); + } + } else { + perVariantRows.push({ + ...rows[index], + protein: '', + sourceId: `${index + 1}`, + }); + } + } + await _pubmed.preLoadCache(conn); const errorList = []; - logger.info(`loading ${rows.length} rows`); + logger.info(`loading ${perVariantRows.length} rows`); - for (let index = 0; index < rows.length; index++) { + for (let index = 0; index < perVariantRows.length; index++) { if (maxRecords && index > maxRecords) { logger.warn(`not loading all content due to max records limit (${maxRecords})`); break; } - const rawRow = rows[index]; - const sourceId = hashRecordToId(rawRow); - logger.info(`processing: ${sourceId} (${index} / ${rows.length})`); + const rawRow = perVariantRows[index]; + logger.info(`processing: ${perVariantRows[index].sourceId} (${index} / ${perVariantRows.length})`); const row = { _raw: rawRow, - sourceId, - ...convertRowFields(HEADER, rows[index]), + protein: perVariantRows[index].protein, + sourceId: perVariantRows[index].sourceId, + ...convertRowFields(HEADER, perVariantRows[index]), }; row.therapy = parseTherapy(row); diff --git a/src/chembl/index.js b/src/chembl/index.js index 8f9f091c..935ca2b3 100644 --- a/src/chembl/index.js +++ b/src/chembl/index.js @@ -47,7 +47,7 @@ const fetchAndLoadById = async (conn, drugId) => { const source = rid(CACHE.SOURCE); const content = { - name: chemblRecord.pref_name, + name: chemblRecord.pref_name || chemblRecord.molecule_properties.full_molformula, source, sourceId: chemblRecord.molecule_chembl_id, }; diff --git a/src/civic/README.md b/src/civic/README.md index f7fae061..8d7f6ffc 100644 --- a/src/civic/README.md +++ b/src/civic/README.md @@ -2,7 +2,7 @@ > :warning: Since this loader produces statements, ontology and vocabulary data should be loaded first -Loads statements into GraphKB using the [CIViC](https://civicdb.org/) REST API. +Loads statements into GraphKB using the [CIViC](https://civicdb.org/) GraphQL API. ```bash node bin/load.js civic @@ -10,7 +10,7 @@ node bin/load.js civic ## About -The `variant.js` module handles all parsing/processing of variant records in CIViC for entry into GraphKB. The corresponding tests are under `test/civic.test.js`. +The `variant.js` module handles all parsing/processing of variant records in CIViC for entry into GraphKB. The corresponding tests are under `test/civic.variant.test.js`. In general this loader attempts to update existing records based on their CIViC evidence ID, however when the mapping from CIViC evidence record to GraphKB statement is not 1 to 1 then the statement is sometimes recreated and the old version soft-deleted instead since we cannot resolve tracking between the 2 for these cases. @@ -24,10 +24,10 @@ node bin/load.js civic --trustedCurators 123 124 ## Mapping Objects from CIViC to GraphKB -In general CIViC (v1) and GraphKB have a lot in common so this mapping can be fairly straightforward. In CIViC Evidence Items are similar to GraphKB statements. However, there are a couple of key differences +In general CIViC (v2.2) and GraphKB have a lot in common so this mapping can be fairly straightforward. In CIViC Evidence Items are similar to GraphKB statements. However, there are a couple of key differences -- In CIViC a list of drugs can be given as "substitutes", in GraphKB these are separate statements -- In CIViC, generally, there is a 1 to 1 relationship with variants and evidence items, in GraphKB any number of variants can be associated with a statement +- In CIViC a list of therapies (formely drugs) can be given as "substitutes", in GraphKB these are separate statements +- In CIViC, while evidence items are assigned a unique molecular profile, each profile can be associated with any number of variants using conditional expression with AND/OR/NOT operators. In GraphKB, statements are associated with any number of variants as implicit necessary conditions (AND operator). Alternative conditions (OR operator) are supported by creating separate statements like with therapy substitutes. Negative conditions (NOT operator) are yet to be supported - GraphKB has a separate field for "appliesTo", this is the target of the statement - Each Evidence Item in CIViC is associated with 1 PubMed article. In GraphKB a statement may have 1-n associated articles. @@ -37,33 +37,44 @@ A mapping between CIViC and GraphKB for Evidence Item vs Statement fields is giv | ------------- | --------------------- | | conditions | disease | | conditions | variant | -| conditions | drug | +| conditions | therapy | | relevance | evidence type | -| relevance | clinical significance | | relevance | evidence direction | +| relevance | significance | | evidence | source | The current implementation of the relevance mapping from CIViC to GraphKB is summarized below -| CIViC Evidence Type | CIViC Evidence Direction | CIViC Clinical Significance | GraphKB Relevance | +| CIViC Evidence Type | CIViC Evidence Direction | CIViC Significance | GraphKB Relevance | | ------------------- | ------------------------ | --------------------------- | ---------------------- | | Diagnostic | Supports | Negative | opposes diagnosis | | Diagnostic | Supports | Positive | favours diagnosis | | Functional | Supports | Dominant Negative | dominant negative | +| Functional | Does Not Support | Dominant Negative | no dominant negative | | Functional | Supports | Gain of Function | gain of function | +| Functional | Does Not Support | Gain of Function | no gain of function | | Functional | Supports | Loss of Function | loss of function | | Functional | Supports | Neomorphic | neomorphic | -| Predictive | Does Not Support | Sensitivity | no response | -| Predictive | Does Not Support | Sensitivity/Response | no response | +| Functional | Does Not Support | Neomorphic | no neomorphic | +| Functional | Supports | Unaltered function | Unaltered function | +| Oncogenic | Supports | Oncogenicity | likely oncogenic | +| Oncogenic | Does Not Support | Oncogenicity | likely benign | | Predictive | Supports | Adverse Response | adverse response | +| Predictive | Supports | Reduced Sensitivity | reduced Sensitivity | | Predictive | Supports | Resistance | resistance | | Predictive | Does Not Support | Resistance | no resistance | -| Predictive | Supports | Sensitivity | sensitivity | | Predictive | Supports | Sensitivity/Response | sensitivity | -| Predisposing | Supports | Likely Pathogenic | likely pathogenic | -| Predisposing | Supports | Pathogenic | pathogenic | -| Predisposing | Supports | Uncertain Significance | likely predisposing | +| Predictive | Does Not Support | Sensitivity/Response | no response | +| Predisposing | Supports | Predisposition | likely predisposing | +| Predisposing | Supports | Protectiveness | likely protective | | Prognostic | Supports | Better Outcome | favourable prognosis | -| Prognostic | Supports | Negative | unfavourable prognosis | | Prognostic | Supports | Poor Outcome | unfavourable prognosis | -| Prognostic | Supports | Positive | favourable prognosis | + +Deprecated CIViC terms still supported for relevance mapping are summarized below + +| CIViC Evidence Type | CIViC Evidence Direction | CIViC Significance | GraphKB Relevance | +| ------------------- | ------------------------ | --------------------------- | ---------------------- | +| Predisposing | Supports | Likely Pathogenic | likely pathogenic | +| Predisposing | Supports | Pathogenic | pathogenic | +| Predisposing | Supports | Positive | Predisposing | +| Predisposing | Supports | Uncertain Significance | likely predisposing | \ No newline at end of file diff --git a/src/civic/disease.js b/src/civic/disease.js new file mode 100644 index 00000000..b1573e93 --- /dev/null +++ b/src/civic/disease.js @@ -0,0 +1,41 @@ +const { orderPreferredOntologyTerms } = require('../graphkb'); + +/** + * Given a CIViC EvidenceItem record with its disease property, + * returns the corresponding disease record from GraphKB + * + * @param {ApiConnection} conn graphkb API connector + * @param {object} param1 + * @param {object} param1.rawRecord the EvidenceItem from CIViC + * @returns {object} the disease record from GraphKB + */ +const getDisease = async (conn, { rawRecord }) => { + let disease; + + // Get corresponding GraphKB Disease by it's doid (disease ontology id) + if (rawRecord.disease) { + let diseaseQueryFilters = {}; + + if (rawRecord.disease.doid) { + diseaseQueryFilters = { + AND: [ + { sourceId: `doid:${rawRecord.disease.doid}` }, + { source: { filters: { name: 'disease ontology' }, target: 'Source' } }, + ], + }; + } else { + diseaseQueryFilters = { name: rawRecord.disease.name }; + } + + disease = await conn.getUniqueRecordBy({ + filters: diseaseQueryFilters, + sort: orderPreferredOntologyTerms, + target: 'Disease', + }); + } + return disease; +}; + +module.exports = { + getDisease, +}; diff --git a/src/civic/evidenceItem.js b/src/civic/evidenceItem.js new file mode 100644 index 00000000..f999f8db --- /dev/null +++ b/src/civic/evidenceItem.js @@ -0,0 +1,297 @@ +const fs = require('fs'); +const path = require('path'); + +const _ = require('lodash'); +const Ajv = require('ajv'); +const { error: { ErrorMixin } } = require('@bcgsc-pori/graphkb-parser'); + +const { checkSpec, request } = require('../util'); +const { logger } = require('../logging'); +const { civic: SOURCE_DEFN } = require('../sources'); +const { EvidenceItem: evidenceSpec } = require('./specs.json'); +const _entrezGene = require('../entrez/gene'); +const { processVariantRecord } = require('./variant'); +const { processMolecularProfile } = require('./profile'); +const { addOrFetchTherapy, resolveTherapies } = require('./therapy'); +const { rid } = require('../graphkb'); + + +class NotImplementedError extends ErrorMixin { } + +// Spec compiler +const ajv = new Ajv(); +const validateEvidenceSpec = ajv.compile(evidenceSpec); + +/** + * Requests evidence items from CIViC using their graphql API + * + * @param {string} url the query url + * @param {object} opt the query options + * @returns {object[]} an array of EvidenceItem records + */ +const requestEvidenceItems = async (url, opt) => { + const body = { ...opt }; + const allRecords = []; + let hasNextPage = true; + + while (hasNextPage) { + try { + const page = await request({ + body, + json: true, + method: 'POST', + uri: url, + }); + allRecords.push(...page.data.evidenceItems.nodes); + body.variables = { + ...body.variables, + after: page.data.evidenceItems.pageInfo.endCursor, + }; + hasNextPage = page.data.evidenceItems.pageInfo.hasNextPage; + } catch (err) { + logger.error(err); + throw (err); + } + } + return allRecords; +}; + +/** + * Fetch CIViC approved evidence entries + * as well as those submitted by trusted curators + * + * @param {string} url the url for the request + * @param {string[]} trustedCurators a list of curator IDs for submitted-only EvidenceItems + * @returns {object} an object with the validated records and the encountered errors + */ +const downloadEvidenceItems = async (url, trustedCurators) => { + const evidenceItems = []; + const query = fs.readFileSync(path.join(__dirname, 'evidenceItems.graphql')).toString(); + + // Get accepted evidenceItems + const accepted = await requestEvidenceItems(url, { + query, + variables: { + status: 'ACCEPTED', + }, + }); + logger.info(`${accepted.length} accepted entries from ${SOURCE_DEFN.name}`); + evidenceItems.push(...accepted); + + // Get submitted evidenceItems from trusted curators + for (const curator of Array.from(new Set(trustedCurators))) { + if (!Number.isNaN(curator)) { + const submittedByATrustedCurator = await requestEvidenceItems(url, { + query, + variables: { + status: 'SUBMITTED', + userId: parseInt(curator, 10), + }, + }); + evidenceItems.push(...submittedByATrustedCurator); + logger.info(`${submittedByATrustedCurator.length} submitted entries by trusted curator ${curator} from ${SOURCE_DEFN.name}`); + } + } + + logger.info(`${evidenceItems.length} total records from ${SOURCE_DEFN.name}`); + + // Validation + const validatedRecords = [], + errors = []; + + for (const record of evidenceItems) { + try { + checkSpec(validateEvidenceSpec, record); + } catch (err) { + errors.push({ error: err, errorMessage: err.toString(), record }); + logger.error(err); + continue; + } + validatedRecords.push(record); + } + + logger.info(`${validatedRecords.length}/${evidenceItems.length} validated records`); + return { errors, records: validatedRecords }; +}; + +/** + * Format one combination from a CIViC EvidenceItem into an object + * ready to be compared with a corresponding GraphKB statement + * + * @param {ApiConnection} conn the API connection object for GraphKB + * @param {object} param1 + * @param {object} param1.record the unparsed record from CIViC + * @param {object} param1.sourceRid the souce rid for CIViC in GraphKB + * @returns {object} the formatted content from one combination + */ +const processCombination = async (conn, { + record: rawRecord, + sourceRid, +}) => { + /* + PROCESSING EVIDENCEITEM DATA SPECIFIC TO THAT COMBINATION/STATEMENT + */ + + // THERAPY + // Get corresponding GraphKB Therapies + let therapy; + + if (rawRecord.therapies) { + try { + therapy = await addOrFetchTherapy( + conn, + sourceRid, + rawRecord.therapies, // therapiesRecords + (rawRecord.therapyInteractionType || '').toLowerCase(), // combinationType + ); + } catch (err) { + throw new Error(`failed to fetch therapy: ${JSON.stringify(rawRecord.therapies)}\nerr:${err}`); + } + } + + // VARIANTS + // Note: the combination can have more than 1 variant + // if the Molecular profile was using AND operators + const { variants: civicVariants } = rawRecord; + const variants = []; + + for (const variant of civicVariants) { + // Variant's Feature + const { feature: { featureInstance } } = variant; + + // TODO: Deal with __typename === 'Factor'. No actual case as April 22nd, 2024 + if (featureInstance.__typename !== 'Gene') { + throw new NotImplementedError( + 'unable to process variant\'s feature of type other than Gene (e.g. Factor)', + ); + } + + let feature; + + try { + [feature] = await _entrezGene.fetchAndLoadByIds(conn, [featureInstance.entrezId]); + } catch (err) { + logger.error(`failed to fetch variant's feature: ${featureInstance.entrezId}`); + throw err; + } + + // Variant + try { + const processedVariants = await processVariantRecord(conn, variant, feature); + logger.verbose(`converted variant name (${variant.name}) to variants (${processedVariants.map(v => v.displayName).join(', and ')})`); + variants.push(...processedVariants); + } catch (err) { + logger.error(`unable to process the variant (id=${rawRecord.variant.id}, name=${rawRecord.variant.name})`); + throw err; + } + } + + /* + FORMATTING CONTENT FOR GRAPHKB STATEMENT + */ + + const { content } = rawRecord; + + // SUBJECT + // Adding Disease as subject + if (rawRecord.evidenceType === 'DIAGNOSTIC' || rawRecord.evidenceType === 'PREDISPOSING') { + if (!content.disease) { + throw new Error('unable to create a diagnostic or predisposing statement without a corresponding disease'); + } + content.subject = content.disease; + } + + // Adding Therapy as subject + if (rawRecord.evidenceType === 'PREDICTIVE' && therapy) { + content.subject = rid(therapy); + } + + // Adding 'patient' Vocabulary as subject + if (rawRecord.evidenceType === 'PROGNOSTIC') { + try { + content.subject = rid( + // get the patient vocabulary object + await conn.getVocabularyTerm('patient'), + ); + } catch (err) { + logger.error('unable to fetch Vocabulary record for term patient'); + throw err; + } + } + + // Adding feature (reference1) or Variant (1st variant as the default) as subject. + if (rawRecord.evidenceType === 'FUNCTIONAL') { + content.subject = rid(variants[0].reference1); + } + if (rawRecord.evidenceType === 'ONCOGENIC') { + content.subject = variants.length === 1 + ? rid(variants[0]) + : rid(variants[0].reference1); + } + + // Checking for Subject + if (!content.subject) { + throw Error('unable to determine statement subject'); + } + + // CONDITIONS + // Adding variants as conditions + content.conditions = [...variants.map(v => rid(v))]; + + // Adding Disease as condition + if (content.disease) { + content.conditions.push(content.disease); + } + delete content.disease; // Removing unwanted properties no longer needed + + // Adding content's subject as condition if not already + if (content.subject && !content.conditions.includes(content.subject)) { + content.conditions.push(content.subject); + } + // Sorting conditions for downstream object comparison + content.conditions.sort(); + + return content; +}; + +/** + * Process an EvidenceItem from CIViC into an array of one or more combinations + * + * @param {object} evidenceItem the CIViC EvidenceItem + * @returns {object[]} an array of combinations + */ +const processEvidenceItem = async (evidenceItem) => { + let record = JSON.parse(JSON.stringify(evidenceItem)); // Deep copy + logger.debug(`processing EvidenceItem ${record.id}`); + + // Resolve therapy combinations if any + // Updates record.therapies and record.therapyInteractionType properties + record = resolveTherapies(record); + + // Molecular Profile (conditions w/ variants) + record.conditions = processMolecularProfile(record.molecularProfile).conditions; + + // PROCESSING EVIDENCEITEM INTO AN ARRAY OF COMBINATIONS + const combinations = []; + + for (const condition of record.conditions) { + for (const therapies of record.therapies) { + const content = JSON.parse(JSON.stringify(record.content)); // Deep copy + combinations.push({ + ..._.omit(record, ['conditions']), + content, + therapies, + variants: [...condition], + }); + } + } + + return combinations; +}; + +module.exports = { + downloadEvidenceItems, + processCombination, + processEvidenceItem, + requestEvidenceItems, +}; diff --git a/src/civic/evidenceItems.graphql b/src/civic/evidenceItems.graphql index 98770e89..2d98ddbd 100644 --- a/src/civic/evidenceItems.graphql +++ b/src/civic/evidenceItems.graphql @@ -2,87 +2,101 @@ query evidenceItems( $after: String $assertionId: Int $before: String - $clinicalSignificance: EvidenceClinicalSignificance $clinicalTrialId: Int $description: String $diseaseId: Int $diseaseName: String - $drugId: Int - $drugName: String $evidenceDirection: EvidenceDirection $evidenceLevel: EvidenceLevel $evidenceRating: Int $evidenceType: EvidenceType $first: Int - $geneSymbol: String $id: Int $last: Int + $molecularProfileId: Int + $molecularProfileName: String $organizationId: Int $phenotypeId: Int + $significance: EvidenceSignificance $sortBy: EvidenceSort $sourceId: Int $status: EvidenceStatusFilter + $therapyId: Int + $therapyName: String $userId: Int $variantId: Int - $variantName: String $variantOrigin: VariantOrigin ) { evidenceItems( after: $after assertionId: $assertionId before: $before - clinicalSignificance: $clinicalSignificance clinicalTrialId: $clinicalTrialId description: $description diseaseId: $diseaseId diseaseName: $diseaseName - drugId: $drugId - drugName: $drugName evidenceDirection: $evidenceDirection evidenceLevel: $evidenceLevel evidenceRating: $evidenceRating evidenceType: $evidenceType first: $first - geneSymbol: $geneSymbol id: $id last: $last + molecularProfileId: $molecularProfileId + molecularProfileName: $molecularProfileName organizationId: $organizationId phenotypeId: $phenotypeId + significance: $significance sortBy: $sortBy sourceId: $sourceId status: $status + therapyId: $therapyId + therapyName: $therapyName userId: $userId variantId: $variantId - variantName: $variantName variantOrigin: $variantOrigin ) { nodes { - clinicalSignificance description disease { doid id name } - drugInteractionType - drugs { - id - name - ncitId - } evidenceDirection evidenceLevel evidenceRating evidenceType - gene { + id + molecularProfile { id name + parsedName { + __typename + ... on MolecularProfileTextSegment { text } + ... on Variant { id } + } + rawName + variants { + feature { + featureInstance { + __typename + ... on Factor { id } + ... on Gene { + entrezId + name + } + } + } + id + name + } } - id phenotypes { hpoId id } + significance source { ascoAbstractId citationId @@ -92,14 +106,12 @@ query evidenceItems( sourceUrl } status - variant { - gene { - entrezId - name - } + therapies { id name + ncitId } + therapyInteractionType } pageCount pageInfo { diff --git a/src/civic/evidenceLevel.js b/src/civic/evidenceLevel.js new file mode 100644 index 00000000..172be882 --- /dev/null +++ b/src/civic/evidenceLevel.js @@ -0,0 +1,59 @@ +/** + * 1-5 : https://docs.civicdb.org/en/latest/model/evidence/evidence_rating.html + * A-E : https://docs.civicdb.org/en/latest/model/evidence/level.html +*/ +const VOCAB = { + 1: 'Claim is not supported well by experimental evidence. Results are not reproducible, or have very small sample size. No follow-up is done to validate novel claims.', + 2: 'Evidence is not well supported by experimental data, and little follow-up data is available. Publication is from a journal with low academic impact. Experiments may lack proper controls, have small sample size, or are not statistically convincing.', + 3: 'Evidence is convincing, but not supported by a breadth of experiments. May be smaller scale projects, or novel results without many follow-up experiments. Discrepancies from expected results are explained and not concerning.', + 4: 'Strong, well supported evidence. Experiments are well controlled, and results are convincing. Any discrepancies from expected results are well-explained and not concerning.', + 5: 'Strong, well supported evidence from a lab or journal with respected academic standing. Experiments are well controlled, and results are clean and reproducible across multiple replicates. Evidence confirmed using independent methods. The study is statistically well powered.', + A: 'Proven/consensus association in human medicine.', + B: 'Clinical trial or other primary patient data supports association.', + C: 'Individual case reports from clinical journals.', + D: 'In vivo or in vitro models support association.', + E: 'Indirect evidence.', + url: 'https://docs.civicdb.org/en/latest/model/evidence.html', +}; + +const EVIDENCE_LEVEL_CACHE = {}; + +/** + * Fetch an evidence level, and add it if there is not an existing record + * + * @param {ApiConnection} conn graphkb API connector + * @param {object} param1 + * @param {object} param1.rawRecord an EvidenceItem record from CIViC + * @param {object} param1.source the CIViC source rid in GraphKB + * @returns {object} an EvidenceLevel recors from GraphKB + */ +const getEvidenceLevel = async (conn, { rawRecord, source, sourceDisplayName }) => { + // get the evidenceLevel + let level = `${rawRecord.evidenceLevel}${rawRecord.evidenceRating || ''}`.toLowerCase(); + + if (EVIDENCE_LEVEL_CACHE[level] === undefined) { + level = await conn.addRecord({ + content: { + description: `${VOCAB[rawRecord.evidenceLevel]} ${VOCAB[rawRecord.evidenceRating] || ''}`, + displayName: `${sourceDisplayName} ${level.toUpperCase()}`, + name: level, + source, + sourceId: level, + url: VOCAB.url, + }, + existsOk: true, + fetchConditions: { + AND: + [{ sourceId: level }, { name: level }, { source }], + }, + target: 'EvidenceLevel', + + }); + EVIDENCE_LEVEL_CACHE[level.sourceId] = level; + } else { + level = EVIDENCE_LEVEL_CACHE[level]; + } + return level; +}; + +module.exports = { getEvidenceLevel }; diff --git a/src/civic/index.js b/src/civic/index.js index 783247f8..2f0aee38 100644 --- a/src/civic/index.js +++ b/src/civic/index.js @@ -1,810 +1,475 @@ /** * @module importer/civic */ -const _ = require('lodash'); -const Ajv = require('ajv'); const fs = require('fs'); -const path = require('path'); -const { error: { ErrorMixin } } = require('@bcgsc-pori/graphkb-parser'); - -const { checkSpec, request } = require('../util'); -const { - orderPreferredOntologyTerms, - rid, - shouldUpdate, -} = require('../graphkb'); +const { rid } = require('../graphkb'); const { logger } = require('../logging'); -const _pubmed = require('../entrez/pubmed'); -const _entrezGene = require('../entrez/gene'); -const { civic: SOURCE_DEFN, ncit: NCIT_SOURCE_DEFN } = require('../sources'); -const { processVariantRecord } = require('./variant'); -const { getPublication } = require('./publication'); -const { EvidenceItem: evidenceSpec } = require('./specs.json'); - -class NotImplementedError extends ErrorMixin {} - -const ajv = new Ajv(); +const { civic: SOURCE_DEFN } = require('../sources'); +const { getDisease } = require('./disease'); +const { getRelevance } = require('./relevance'); +const { getEvidenceLevel } = require('./evidenceLevel'); +const { getPublication, loadPubmedCache } = require('./publication'); +const { + downloadEvidenceItems, + processCombination, + processEvidenceItem, +} = require('./evidenceItem'); +const { + contentMatching, + createStatement, + deleteStatements, + needsUpdate, + updateStatement, +} = require('./statement'); const BASE_URL = 'https://civicdb.org/api/graphql'; -/** - * 1-5 : https://docs.civicdb.org/en/latest/model/evidence/evidence_rating.html - * A-E : https://docs.civicdb.org/en/latest/model/evidence/level.html - */ -const VOCAB = { - 1: 'Claim is not supported well by experimental evidence. Results are not reproducible, or have very small sample size. No follow-up is done to validate novel claims.', - 2: 'Evidence is not well supported by experimental data, and little follow-up data is available. Publication is from a journal with low academic impact. Experiments may lack proper controls, have small sample size, or are not statistically convincing.', - 3: 'Evidence is convincing, but not supported by a breadth of experiments. May be smaller scale projects, or novel results without many follow-up experiments. Discrepancies from expected results are explained and not concerning.', - 4: 'Strong, well supported evidence. Experiments are well controlled, and results are convincing. Any discrepancies from expected results are well-explained and not concerning.', - 5: 'Strong, well supported evidence from a lab or journal with respected academic standing. Experiments are well controlled, and results are clean and reproducible across multiple replicates. Evidence confirmed using independent methods. The study is statistically well powered.', - A: 'Proven/consensus association in human medicine.', - B: 'Clinical trial or other primary patient data supports association.', - C: 'Individual case reports from clinical journals.', - D: 'In vivo or in vitro models support association.', - E: 'Indirect evidence.', - url: 'https://docs.civicdb.org/en/latest/model/evidence.html', -}; - -const EVIDENCE_LEVEL_CACHE = {}; // avoid unecessary requests by caching the evidence levels -const RELEVANCE_CACHE = {}; - -const validateEvidenceSpec = ajv.compile(evidenceSpec); - /** - * Requests evidence items from CIViC using their graphql API + * Increment counter on GraphKB Statement CRUD operations + * + * @param {object} initial the counter + * @param {object} updates the increment to apply + * @returns {object} the incremented counter */ -const requestEvidenceItems = async (url, opt) => { - const allRecords = []; - let hasNextPage = true; - - while (hasNextPage) { - try { - const page = await request({ - body: { ...opt }, - json: true, - method: 'POST', - uri: url, - }); - allRecords.push(...page.data.evidenceItems.nodes); - opt.variables = { ...opt.variables, after: page.data.evidenceItems.pageInfo.endCursor }; - hasNextPage = page.data.evidenceItems.pageInfo.hasNextPage; - } catch (err) { - logger.error(err); - throw (err); - } +const incrementCounts = (initial, updates) => { + if (!initial) { + return updates; } - return allRecords; -}; + // deep copy + const updated = JSON.parse(JSON.stringify(initial)); -/** - * Extract the appropriate GraphKB relevance term from a CIViC evidence record - */ -const translateRelevance = (evidenceType, evidenceDirection, clinicalSignificance) => { - if (evidenceDirection === 'DOES_NOT_SUPPORT') { - if (evidenceType === 'PREDICTIVE') { - switch (clinicalSignificance) { // eslint-disable-line default-case - case 'SENSITIVITYRESPONSE': { - return 'no response'; - } - - case 'RESISTANCE': { return 'no resistance'; } - } - } - } else if (evidenceDirection === 'SUPPORTS') { - switch (evidenceType) { // eslint-disable-line default-case - case 'PREDICTIVE': { - switch (clinicalSignificance) { // eslint-disable-line default-case - case 'ADVERSE_RESPONSE': - case 'REDUCED_SENSITIVITY': - - case 'RESISTANCE': { - return clinicalSignificance.replace(/_/g, ' ').toLowerCase(); - } - - case 'SENSITIVITYRESPONSE': { return 'sensitivity'; } - } - break; - } - - case 'FUNCTIONAL': { - return clinicalSignificance.replace(/_/g, ' ').toLowerCase(); - } - - case 'DIAGNOSTIC': { - switch (clinicalSignificance) { // eslint-disable-line default-case - case 'POSITIVE': { return 'favours diagnosis'; } - - case 'NEGATIVE': { return 'opposes diagnosis'; } - } - break; - } - - case 'PROGNOSTIC': { - switch (clinicalSignificance) { // eslint-disable-line default-case - case 'NEGATIVE': - - case 'POOR_OUTCOME': { - return 'unfavourable prognosis'; - } - case 'POSITIVE': - - case 'BETTER_OUTCOME': { - return 'favourable prognosis'; - } - } - break; - } - - case 'PREDISPOSING': { - if (['POSITIVE', null].includes(clinicalSignificance)) { - return 'predisposing'; - } if (clinicalSignificance.includes('PATHOGENIC')) { - return clinicalSignificance.replace(/_/g, ' ').toLowerCase(); - } if (clinicalSignificance === 'UNCERTAIN_SIGNIFICANCE') { - return 'likely predisposing'; - } - break; - } + for (const level1 of Object.keys(updated)) { + for (const level2 of Object.keys(updated[level1])) { + updated[level1][level2] += updates[level1][level2]; } } - throw new NotImplementedError( - `unable to process relevance (${JSON.stringify( - { clinicalSignificance, evidenceDirection, evidenceType }, - )})`, - ); -}; - - -/** - * Convert the CIViC relevance types to GraphKB terms - */ -const getRelevance = async ({ rawRecord, conn }) => { - // translate the type to a GraphKB vocabulary term - let relevance = translateRelevance( - rawRecord.evidenceType, - rawRecord.evidenceDirection, - rawRecord.clinicalSignificance, - ).toLowerCase(); - - if (RELEVANCE_CACHE[relevance] === undefined) { - relevance = await conn.getVocabularyTerm(relevance); - RELEVANCE_CACHE[relevance.name] = relevance; - } else { - relevance = RELEVANCE_CACHE[relevance]; - } - return relevance; + return updated; }; /** - * Given some drug name, find the drug that is equivalent by name in GraphKB + * Access the CIVic API, parse content, transform and load into GraphKB + * + * @param {object} param0 + * @param {ApiConnection} param0.conn the api connection object for GraphKB + * @param {string} param0.errorLogPrefix prefix to the generated error json file + * @param {number} param0.maxRecords limit of EvidenceItem records to be processed and upload + * @param {?boolean} param0.noUpdate for avoiding deletion/update of existing GraphKB Statements + * @param {string[]} param0.trustedCurators a list of curator IDs for submitted-only EvidenceItems + * @param {?string} param0.url url to use as the base for accessing the civic ApiConnection */ -const getDrug = async (conn, drugRecord) => { - let originalError; - - // fetch from NCIt first if possible, or pubchem - // then use the name as a fallback - const name = drugRecord.name.toLowerCase().trim(); - - if (drugRecord.ncitId) { - try { - const drug = await conn.getUniqueRecordBy({ - filters: [ - { source: { filters: { name: NCIT_SOURCE_DEFN.name }, target: 'Source' } }, - { sourceId: drugRecord.ncitId }, - { name: drugRecord.name }, - ], - sort: orderPreferredOntologyTerms, - target: 'Therapy', - }); - return drug; - } catch (err) { - logger.error(`had NCIt drug mapping (${drugRecord.ncitId}) named (${drugRecord.name}) but failed to fetch from graphkb: ${err}`); - throw err; - } - } +const upload = async ({ + conn, + errorLogPrefix, + maxRecords, + noUpdate = false, + trustedCurators, + url = BASE_URL, +}) => { + const countsEI = { + error: 0, + partialSuccess: 0, + skip: 0, + success: 0, + }; + let countsST; - try { - const drug = await conn.getTherapy(name); - return drug; - } catch (err) { - originalError = err; - } + // Adding CIViC as source if not already in GraphKB + const source = await conn.addSource(SOURCE_DEFN); + const sourceRid = rid(source); - try { - const match = /^\s*(\S+)\s*\([^)]+\)$/.exec(name); + /* + 1. DOWNLOAD & PREPROCESSING + */ - if (match) { - return await conn.getTherapy(match[1]); - } - } catch (err) { } - logger.error(originalError); - throw originalError; -}; + // GETTING CIVIC EVIDENCEITEMS FROM CIVIC API + // Evidences accepted, or submitted from a trusted curator + logger.info(`loading evidenceItems from ${url}`); + const { + errors: downloadEvidenceItemsErr, + records: evidenceItems, + } = await downloadEvidenceItems(url, trustedCurators); + // Validation errors + const validationErrorList = []; -/** * - * Add or fetch an drug combination if there is not an existing record - * Link the drug combination to its individual elements - */ -const addOrFetchDrug = async (conn, source, drugsRecords, combinationType) => { - if (drugsRecords.length <= 1) { - if (drugsRecords[0] === null) { - return null; - } - return getDrug(conn, drugsRecords[0]); + if (downloadEvidenceItemsErr.length > 0) { + countsEI.error += downloadEvidenceItemsErr.length; + validationErrorList.push(...downloadEvidenceItemsErr); } - const drugs = await Promise.all(drugsRecords.map(async drug => getDrug(conn, drug))); - const sourceId = drugs.map(e => e.sourceId).sort().join(' + '); - const name = drugs.map(e => e.name).sort().join(' + '); - const combinedTherapy = await conn.addRecord({ - content: { - combinationType, name, source: rid(source), sourceId, - }, - existsOk: true, - target: 'Therapy', - }); - for (const drug of drugs) { - await conn.addRecord({ - content: { - in: rid(combinedTherapy), out: rid(drug), source: rid(source), - }, - existsOk: true, - target: 'ElementOf', - }); + // GETTING CIVIC STATEMENTS FROM GRAPHKB API + // Note: One or more GKB Statement can come from the same CIVIC id (sourceId) + logger.info('loading related statements from GraphKB'); + const statements = await conn.getRecords({ + filters: { source: sourceRid }, + returnProperties: [ + '@rid', + 'conditions', + 'description', + 'evidence', + 'evidenceLevel', + 'relevance', + 'reviewStatus', + 'source', + 'sourceId', + 'subject', + ], + target: 'Statement', + }); + const sourceIdsFromGKB = new Set(statements.map(r => r.sourceId)); + logger.info(`${sourceIdsFromGKB.size} distinct ${SOURCE_DEFN.name} sourceId in GraphKB statements`); + logger.info(`${statements.length} total statements previously added to GraphKB from ${SOURCE_DEFN.name}`); + + // REFACTORING GRAPHKB STATEMENTS INTO STATEMENTSBYSOURCEID + // where each sourceId is a key associated with an array + // of one or more GKB Statement records + const statementsBySourceId = {}; + + for (const record of statements) { + if (!statementsBySourceId[record.sourceId]) { + statementsBySourceId[record.sourceId] = []; + } + // Sorting conditions for downstream object comparison + record.conditions.sort(); + statementsBySourceId[record.sourceId].push(record); } - return combinedTherapy; -}; - -const getEvidenceLevel = async ({ - conn, rawRecord, sources, -}) => { - // get the evidenceLevel - let level = `${rawRecord.evidenceLevel}${rawRecord.evidenceRating || ''}`.toLowerCase(); - - if (EVIDENCE_LEVEL_CACHE[level] === undefined) { - level = await conn.addRecord({ - content: { - description: `${VOCAB[rawRecord.evidenceLevel]} ${VOCAB[rawRecord.evidenceRating] || ''}`, - displayName: `${SOURCE_DEFN.displayName} ${level.toUpperCase()}`, - name: level, - source: rid(sources.civic), - sourceId: level, - url: VOCAB.url, - }, - existsOk: true, - fetchConditions: { - AND: - [{ sourceId: level }, { name: level }, { source: rid(sources.civic) }], - }, - target: 'EvidenceLevel', - - }); - EVIDENCE_LEVEL_CACHE[level.sourceId] = level; - } else { - level = EVIDENCE_LEVEL_CACHE[level]; + // REFACTORING CIVIC EVIDENCEITEMS INTO EVIDENCEITEMSBYID + // where each id is a key associated with one CIViC EvidenceItem as value + logger.info(`Pre-pocessing ${evidenceItems.length} records`); + const evidenceItemsById = {}; + + // Performing some checks. Skipping some records if needed + // eslint-disable-next-line guard-for-in + for (const i in evidenceItems) { + // Check if max records limit has been reached + if (maxRecords && Object.keys(evidenceItemsById).length >= maxRecords) { + logger.warn(`Not loading all content due to max records limit (${maxRecords})`); + countsEI.skip += (evidenceItems.length - i); + break; + } + // Check if record id is unique + if (evidenceItemsById[evidenceItems[i].id]) { + logger.error(`Multiple Evidence Items with the same id: ${evidenceItems[i].id}. Violates assumptions. Only the 1st one was kept.`); + countsEI.skip++; + continue; + } + // Adding EvidenceItem to object for upload + evidenceItemsById[evidenceItems[i].id] = evidenceItems[i]; } - return level; -}; - - -/** - * Transform a CIViC evidence record into a GraphKB statement - * - * @param {object} opt - * @param {ApiConnection} opt.conn the API connection object for GraphKB - * @param {object} opt.rawRecord the unparsed record from CIViC - * @param {object} opt.sources the sources by name - * @param {boolean} opt.oneToOne civic statements to graphkb statements is a 1 to 1 mapping - * @param {object} opt.variantsCache used to avoid repeat processing of civic variants. stores the graphkb variant(s) if success or the error if not - * @param - */ -const processEvidenceRecord = async (opt) => { - const { - conn, rawRecord, sources, variantsCache, oneToOne = false, - } = opt; - - const [level, relevance, [feature]] = await Promise.all([ - getEvidenceLevel(opt), - getRelevance(opt), - _entrezGene.fetchAndLoadByIds(conn, [rawRecord.variant.gene.entrezId]), - ]); - let variants; - - if (variantsCache.records[rawRecord.variant.id]) { - variants = variantsCache.records[rawRecord.variant.id]; - } else if (variantsCache.errors[rawRecord.variant.id]) { - throw variantsCache.errors[rawRecord.variant.id]; - } else { + const noRecords = Object.keys(evidenceItemsById).length; + logger.info(`${noRecords}/${evidenceItems.length} Evidence Items to process`); + + /* + 2. PROCESSING EACH CIVIC EVIDENCEITEM INTO ONE OR MORE GKB STATEMENTS + */ + + // PubMed caching + logger.info('Caching Pubmed publication'); + await loadPubmedCache(conn); + + // Keeping track of EvidenceItem sourceIds who raised errors during processing + const errorSourceIds = { + disease: new Map(), + evidence: new Map(), + evidenceLevel: new Map(), + individualCombinationProcessing: new Map(), + processingIntoCombinations: new Map(), + relevance: new Map(), + }; + const casesToReview = new Map(); + + logger.info(`\n\n${'#'.repeat(80)}\n## PROCESSING RECORDS\n${'#'.repeat(80)}\n`); + let recordNumber = 0; + + // MAIN LOOP + // Looping through Evidence Items + for (const [id, evidenceItem] of Object.entries(evidenceItemsById)) { + /* PROCESSING EVIDENCEITEMS */ + + recordNumber++; + logger.info(); + logger.info(`***** ${recordNumber}/${noRecords} : processing id ${id} *****`); + + const numberOfStatements = statementsBySourceId[id] + ? statementsBySourceId[id].length + : 0; + logger.info(`${numberOfStatements} related statement(s)`); + + // Base object (properties order matters) + // Common content will be deep copied downstream for each combination + evidenceItem.content = { + conditions: [], + description: evidenceItem.description || '', + evidence: [], + evidenceLevel: [], + relevance: undefined, + reviewStatus: (evidenceItem.status === 'ACCEPTED' + ? 'not required' + : 'pending' + ), + source: sourceRid, + sourceId: id, + subject: undefined, + }; + + // PROCESSING DATA COMMON TO ALL COMBINATIONS + + // Removing extra spaces in description. Needed before content comparison + evidenceItem.content.description = evidenceItem.content.description.replace(/\s+/g, ' ').trim(); + + // Get evidence (publication) rid try { - variants = await processVariantRecord(conn, rawRecord.variant, feature); - variantsCache.records[rawRecord.variant.id] = variants; - logger.verbose(`converted variant name (${rawRecord.variant.name}) to variants (${variants.map(v => v.displayName).join(', and ')})`); + evidenceItem.content.evidence.push(rid( + await getPublication(conn, evidenceItem), + )); } catch (err) { - variantsCache.errors[rawRecord.variant.id] = err; - logger.error(`evidence (${rawRecord.id}) Unable to process the variant (id=${rawRecord.variant.id}, name=${rawRecord.variant.name}): ${err}`); - throw err; + logger.error(err); + countsEI.error++; + errorSourceIds.evidence.set(id, err); + continue; } - } - - // get the disease by doid - let disease; - - // find the disease if it is not null - if (rawRecord.disease) { - let diseaseQueryFilters = {}; - - if (rawRecord.disease.doid) { - diseaseQueryFilters = { - AND: [ - { sourceId: `doid:${rawRecord.disease.doid}` }, - { source: { filters: { name: 'disease ontology' }, target: 'Source' } }, - ], - }; - } else { - diseaseQueryFilters = { name: rawRecord.disease.name }; + // Get evidenceLevel rid + try { + evidenceItem.content.evidenceLevel.push(rid( + await getEvidenceLevel(conn, { + rawRecord: evidenceItem, + source: sourceRid, + sourceDisplayName: SOURCE_DEFN.displayName, + }), + )); + } catch (err) { + logger.error(err); + countsEI.error++; + errorSourceIds.evidenceLevel.set(id, err); + continue; } - disease = await conn.getUniqueRecordBy({ - filters: diseaseQueryFilters, - sort: orderPreferredOntologyTerms, - target: 'Disease', - }); - } - // get the drug(s) by name - let drug; - - if (rawRecord.drugs) { + // Get relevance rid try { - drug = await addOrFetchDrug( - conn, - rid(sources.civic), - rawRecord.drugs, - (rawRecord.drugInteractionType || '').toLowerCase(), + evidenceItem.content.relevance = rid( + await getRelevance(conn, { rawRecord: evidenceItem }), ); } catch (err) { logger.error(err); - logger.error(`failed to fetch drug: ${JSON.stringify(rawRecord.drugs)}`); - throw err; + countsEI.error++; + errorSourceIds.relevance.set(id, err); + continue; } - } - const publication = await getPublication(conn, rawRecord); - - // common content - const content = { - conditions: [...variants.map(v => rid(v))], - description: rawRecord.description, - evidence: [rid(publication)], - evidenceLevel: [rid(level)], - relevance: rid(relevance), - reviewStatus: (rawRecord.status === 'ACCEPTED' - ? 'not required' - : 'pending' - ), - source: rid(sources.civic), - sourceId: rawRecord.id, - }; - - // create the statement and connecting edges - if (rawRecord.evidenceType === 'DIAGNOSTIC' || rawRecord.evidenceType === 'PREDISPOSING') { - if (!disease) { - throw new Error('Unable to create a diagnostic or predisposing statement without a corresponding disease'); + // Get disease rid + try { + // Will be removed downstream after being used as content's subject and/or condition + evidenceItem.content.disease = rid( + await getDisease(conn, { rawRecord: evidenceItem }), + true, // nullOk=true since some EvidenceItems aren't related to any specific disease + ); + } catch (err) { + logger.error(err); + countsEI.error++; + errorSourceIds.disease.set(id, err); + continue; } - content.subject = rid(disease); - } else if (disease) { - content.conditions.push(rid(disease)); - } - if (rawRecord.evidenceType === 'PREDICTIVE' && drug) { - content.subject = rid(drug); - } if (rawRecord.evidenceType === 'PROGNOSTIC') { - // get the patient vocabulary object - content.subject = rid(await conn.getVocabularyTerm('patient')); - } if (rawRecord.evidenceType === 'FUNCTIONAL') { - content.subject = rid(feature); - } - - if (content.subject && !content.conditions.includes(content.subject)) { - content.conditions.push(content.subject); - } + // PROCESSING INDIVIDUAL EVIDENCEITEM INTO AN ARRAY OF COMBINATIONS + // (One combination per expected GraphKB statement) + const combinations = []; - if (!content.subject) { - throw Error(`unable to determine statement subject for evidence (${content.sourceId}) record`); - } + try { + combinations.push(...await processEvidenceItem(evidenceItem)); + } catch (err) { + logger.error(err); + countsEI.error++; + errorSourceIds.processingIntoCombinations.set(id, err); + continue; + } + logger.info(`${combinations.length} combination(s)`); - const fetchConditions = [ - { sourceId: content.sourceId }, - { source: content.source }, - { evidence: content.evidence }, // civic evidence items are per publication - ]; - - if (!oneToOne) { - fetchConditions.push(...[ - { relevance: content.relevance }, - { subject: content.subject }, - { conditions: content.conditions }, - ]); - } + // PROCESSING INDIVIDUAL COMBINATION + // Formatting each combination's content for GraphKB statement requirements + const contents = []; + let processCombinationErrors = 0; - let original; - - if (oneToOne) { - // get previous iteration - const originals = await conn.getRecords({ - filters: { - AND: [ - { source: rid(sources.civic) }, - { sourceId: rawRecord.id }, - ], - }, - target: 'Statement', - }); + for (const combination of combinations) { + try { + contents.push( + await processCombination(conn, { + record: combination, + sourceRid, + }), + ); + } catch (err) { + logger.error(err); + processCombinationErrors++; - if (originals.length > 1) { - throw Error(`Supposed to be 1to1 mapping between graphKB and civic but found multiple records with source ID (${rawRecord.id})`); - } - if (originals.length === 1) { - [original] = originals; - - const excludeTerms = [ - '@rid', - '@version', - 'comment', - 'createdAt', - 'createdBy', - 'reviews', - 'updatedAt', - 'updatedBy', - ]; - - if (!shouldUpdate('Statement', original, content, excludeTerms)) { - return original; + if (!errorSourceIds.individualCombinationProcessing.get(id)) { + errorSourceIds.individualCombinationProcessing.set(id, []); + } + const v = errorSourceIds.individualCombinationProcessing.get(id); + errorSourceIds.individualCombinationProcessing.set(id, [...v, err]); } } - } - - if (original) { - // update the existing record - return conn.updateRecord('Statement', rid(original), content); - } - // create a new record - return conn.addRecord({ - content, - existsOk: true, - fetchConditions: { - AND: fetchConditions, - }, - target: 'Statement', - upsert: true, - upsertCheckExclude: [ - 'comment', - 'displayNameTemplate', - 'reviews', - ], - }); -}; + const successRatio = `${combinations.length - processCombinationErrors}/${combinations.length}`; + const processCombinationsMsg = `Processed ${successRatio} combination(s)`; -/** - * Get a list of CIViC Evidence Items which have since been deleted. - * Returns the list of evidence item IDs to be purged from GraphKB - * - * @param {string} url endpoint for the CIViC API - */ -const fetchDeletedEvidenceItems = async (url) => { - const ids = new Set(); - - // Get rejected evidenceItems - logger.info(`loading rejected evidenceItems from ${url}`); - const rejected = await requestEvidenceItems(url, { - query: `query evidenceItems($after: String, $status: EvidenceStatusFilter) { - evidenceItems(after: $after, status: $status) { - nodes {id} - pageCount - pageInfo {endCursor, hasNextPage} - totalCount - } - }`, - variables: { - status: 'REJECTED', - }, - }); - rejected.forEach(node => ids.add(node.id)); - logger.info(`fetched ${ids.size} rejected entries from CIViC`); - return ids; -}; + // If at least some combinations succeeds, then it's a success + if (processCombinationErrors === 0) { + countsEI.success++; + logger.info(processCombinationsMsg); + } else if (processCombinationErrors < combinations.length) { + countsEI.partialSuccess++; + logger.warn(processCombinationsMsg); + } else { + countsEI.error++; + logger.error(processCombinationsMsg); + } -/** - * Fetch civic approved evidence entries as well as those submitted by trusted curators - * - * @param {string} url the endpoint for the request - * @param {string[]} trustedCurators a list of curator IDs to also fetch submitted only evidence items for - */ -const downloadEvidenceRecords = async (url, trustedCurators) => { - const records = []; - const errorList = []; - const counts = { - error: 0, exists: 0, skip: 0, success: 0, - }; + /* MATCHING EVIDENCEITEMS WITH STATEMENTS */ - const evidenceItems = []; - const query = fs.readFileSync(path.join(__dirname, 'evidenceItems.graphql')).toString(); + // Content matching between CIViC and GraphKB records + // so we know which CRUD operation to perform on each statement + const { toCreate, toDelete, toUpdate } = contentMatching({ + allFromCivic: contents, + allFromGkb: statementsBySourceId[id] || [], + }); - // Get accepted evidenceItems - logger.info(`loading accepted evidenceItems from ${url}`); - const accepted = await requestEvidenceItems(url, { - query, - variables: { - status: 'ACCEPTED', - }, - }); - logger.info(`fetched ${accepted.length} accepted entries from CIViC`); - evidenceItems.push(...accepted); - - // Get submitted evidenceItems from trusted curators - for (const curator of Array.from(new Set(trustedCurators))) { - if (!Number.isNaN(curator)) { - logger.info(`loading submitted evidenceItems by trusted curator ${curator} from ${url}`); - const submittedByATrustedCurator = await requestEvidenceItems(url, { - query, - variables: { - status: 'SUBMITTED', - userId: parseInt(curator, 10), - }, - }); - evidenceItems.push(...submittedByATrustedCurator); + /* CREATE/UPDATE/DELETE STATEMENTS */ + + const loaclCountsST = { + create: { err: 0, success: 0 }, + delete: { err: 0, success: 0 }, + noUpdateNeeded: { success: 0 }, + update: { err: 0, success: 0 }, + }; + + // UPDATE + if (!noUpdate && toUpdate.length > 0) { + for (let i = 0; i < toUpdate.length; i++) { + const { fromCivic, fromGkb } = toUpdate[i]; + + // Check if an update is needed to avoid unnecessary API calls + if (needsUpdate({ fromCivic, fromGkb })) { + const updatedCount = await updateStatement(conn, { fromCivic, fromGkb }); + loaclCountsST.update.err += updatedCount.err; + loaclCountsST.update.success += updatedCount.success; + } else { + loaclCountsST.noUpdateNeeded.success++; + } + } } - } - const submittedCount = evidenceItems.length - accepted.length; - logger.info(`loaded ${submittedCount} unaccepted entries by trusted submitters from CIViC`); - // Validation - for (const record of evidenceItems) { - try { - checkSpec(validateEvidenceSpec, record); - } catch (err) { - errorList.push({ error: err, errorMessage: err.toString(), record }); - logger.error(err); - counts.error++; - continue; - } + // DELETE + if (!noUpdate && toDelete.length > 0) { + const rids = toDelete.map(el => el['@rid']); - if ( - record.clinicalSignificance === 'NA' - || (record.clinicalSignificance === null && record.evidenceType === 'PREDICTIVE') - ) { - counts.skip++; - logger.debug(`skipping uninformative record (${record.id})`); - } else { - records.push(record); + if (processCombinationErrors > 0) { + // Do not delete any statements if some combinations have processing errors + logger.info(`${toDelete.length} unmatched statement(s) to be reviewed for deletion`); + casesToReview.set(id, rids); + } else { + loaclCountsST.delete = await deleteStatements(conn, { rids }); + } } - } - return { counts, errorList, records }; -}; - - -/** - * Access the CIVic API, parse content, transform and load into GraphKB - * - * @param {object} opt options - * @param {ApiConnection} opt.conn the api connection object for GraphKB - * @param {string} [opt.url] url to use as the base for accessing the civic ApiConnection - * @param {string[]} opt.trustedCurators a list of curator IDs to also fetch submitted only evidence items for - */ -const upload = async ({ - conn, errorLogPrefix, trustedCurators, ignoreCache = false, maxRecords, url = BASE_URL, -}) => { - const source = await conn.addSource(SOURCE_DEFN); - // Get list of all previous statements from CIVIC in GraphKB - let previouslyEntered = await conn.getRecords({ - filters: { source: rid(source) }, - returnProperties: ['sourceId'], - target: 'Statement', - }); - previouslyEntered = new Set(previouslyEntered.map(r => r.sourceId)); - logger.info(`Found ${previouslyEntered.size} records previously added from ${SOURCE_DEFN.name}`); - // Get list of all Pubmed publication reccords from GraphKB - logger.info('caching publication records'); - _pubmed.preLoadCache(conn); - - // Get evidence records from CIVIC (Accepted, or Submitted from a trusted curator) - const { counts, errorList, records } = await downloadEvidenceRecords(url, trustedCurators); - // Get rejected evidence records ids from CIVIC - const purgeableEvidenceItems = await fetchDeletedEvidenceItems(url); - - logger.info(`Processing ${records.length} records`); - // keep track of errors and already processed variants by their CIViC ID to avoid repeat logging - const variantsCache = { - errors: {}, - records: {}, - }; - - // Refactor records into recordsById and varById - const recordsById = {}; - const varById = {}; - - for (const record of records) { - if (!recordsById[record.id]) { - recordsById[record.id] = []; + // CREATE + if (toCreate.length > 0) { + for (let i = 0; i < toCreate.length; i++) { + const createdCount = await createStatement(conn, { fromCivic: toCreate[i] }); + loaclCountsST.create.err += createdCount.err; + loaclCountsST.create.success += createdCount.success; + } } - recordsById[record.id].push(record); - if (maxRecords && Object.values(recordsById).length >= maxRecords) { - logger.warn(`not loading all content due to max records limit (${maxRecords})`); - break; + // logging + for (const level of Object.keys(loaclCountsST)) { + if (loaclCountsST[level].err > 0 || loaclCountsST[level].success > 0) { + logger.info(`${level}: ${JSON.stringify(loaclCountsST[level])}`); + } } + countsST = incrementCounts(countsST, loaclCountsST); - if (record.variant && record.variant.id) { - varById[record.variant.id.toString()] = record.variant; - } + // END OF MAIN LOOP } + logger.info(`\n\n${'#'.repeat(80)}\n## END OF RECORD PROCESSING\n${'#'.repeat(80)}\n`); - // Main loop on recordsById - for (const [sourceId, recordList] of Object.entries(recordsById)) { - if (previouslyEntered.has(sourceId) && !ignoreCache) { - counts.exists++; - continue; - } - if (purgeableEvidenceItems.has(sourceId)) { - // this should never happen. If it does we have made an invalid assumption about how civic uses IDs. - throw new Error(`Record ID is both deleted and to-be loaded. Violates assumptions: ${sourceId}`); - } - const preupload = new Set((await conn.getRecords({ - filters: [ - { source: rid(source) }, { sourceId }, - ], - target: 'Statement', - })).map(rid)); - - let mappedCount = 0; - const postupload = []; - - // Resolve combinations - for (const record of recordList) { - // Splits civic evidence items drugs into separate evidence items based on their combination type. - if (record.drugs === null || record.drugs.length === 0) { - record.drugs = [null]; - } else if ( - record.drugInteractionType === 'COMBINATION' - || record.drugInteractionType === 'SEQUENTIAL' - ) { - record.drugs = [record.drugs]; - } else if (record.drugInteractionType === 'SUBSTITUTES' || record.drugs.length < 2) { - record.drugs = record.drugs.map(drug => [drug]); - record.drugInteractionType = null; - } else { - logger.error(`(evidence: ${record.id}) unsupported drug interaction type (${record.drugInteractionType}) for a multiple drug (${record.drugs.length}) statement`); - counts.skip++; - continue; - } + // Logging EvidenceItem processing counts + logger.info(); + logger.info('***** CIViC EvidenceItem records processing report: *****'); + logger.info(JSON.stringify(countsEI)); - // Splits variants into a list to indicate separate evidence items when variants have been linked as "or" - record.variants = [varById[record.variant.id.toString()]]; // OR-ing of variants - let orCombination; + // Logging detailed EvidenceItem processing counts + logger.info('Processing errors report:'); - if (orCombination = /^([a-z]\d+)([a-z])\/([a-z])$/i.exec(record.variants[0].name)) { - const [, prefix, tail1, tail2] = orCombination; - record.variants = [ - { ...record.variants[0], name: `${prefix}${tail1}` }, - { ...record.variants[0], name: `${prefix}${tail2}` }, - ]; - } - mappedCount += record.variants.length * record.drugs.length; - } + for (const [key, value] of Object.entries(errorSourceIds)) { + logger.info(`${key}: ${value.size}`); + // Also formatting Maps into objects for saving to file downstream + errorSourceIds[key] = Object.fromEntries(errorSourceIds[key]); + } - const oneToOne = mappedCount === 1 && preupload.size === 1; - - // Upload all GraphKB statements for this CIViC Evidence Item - for (const record of recordList) { - for (const variant of record.variants) { - for (const drugs of record.drugs) { - try { - logger.debug(`processing ${record.id}`); - const result = await processEvidenceRecord({ - conn, - oneToOne, - rawRecord: { ..._.omit(record, ['drugs', 'variants']), drugs, variant }, - sources: { civic: source }, - variantsCache, - }); - postupload.push(rid(result)); - counts.success += 1; - } catch (err) { - if ( - err.toString().includes('is not a function') - || err.toString().includes('of undefined') - ) { - console.error(err); - } - if (err instanceof NotImplementedError) { - // accepted evidence that we do not support loading. Should delete as it may have changed from something we did support - purgeableEvidenceItems.add(sourceId); - } - errorList.push({ error: err, errorMessage: err.toString(), record }); - logger.error(`evidence (${record.id}) ${err}`); - counts.error += 1; - } - } - } - } + // DELETING UNWANTED GRAPHKB STATEMENTS + // sourceIds no longer in CIViC (not accepted/submitted by trustedCurators) but still in GraphKB + const allIdsFromCivic = new Set(evidenceItems.map(r => r.id.toString())); + const toDelete = new Set([...sourceIdsFromGKB].filter(x => !allIdsFromCivic.has(x))); + logger.info(); + logger.info('***** Deprecated items *****'); + logger.warn(`${toDelete.size} deprecated ${SOURCE_DEFN.name} Evidence Items still in GraphKB Statement`); - // compare statments before/after upload to determine if any records should be soft-deleted - postupload.forEach((id) => { - preupload.delete(id); - }); + if (toDelete.size > 0) { + logger.info(`sourceIds: ${Array.from(toDelete)}`); + } - if (preupload.size && purgeableEvidenceItems.has(sourceId)) { - logger.warn(` - Removing ${preupload.size} CIViC Entries (EID:${sourceId}) of unsupported format - `); + // GraphKB Statements Soft-deletion + if (!noUpdate && toDelete.size > 0) { + const deletedCount = await deleteStatements(conn, { + source: sourceRid, + sourceIds: Array.from(toDelete), + }); + const attempts = deletedCount.success + deletedCount.err; + logger.info(`${deletedCount.success}/${attempts} soft-deleted statements`); - try { - await Promise.all( - Array.from(preupload).map( - async outdatedId => conn.deleteRecord('Statement', outdatedId), - ), - ); - } catch (err) { - logger.error(err); - } - } else if (preupload.size) { - if (postupload.length) { - logger.warn(`deleting ${preupload.size} outdated statement records (${Array.from(preupload).join(' ')}) has new/retained statements (${postupload.join(' ')})`); - - try { - await Promise.all( - Array.from(preupload).map( - async outdatedId => conn.deleteRecord('Statement', outdatedId), - ), - ); - } catch (err) { - logger.error(err); - } - } else { - logger.error(`NOT deleting ${preupload.size} outdated statement records (${Array.from(preupload).join(' ')}) because failed to create replacements`); - } + if (countsST) { + countsST.delete.err += deletedCount.err; + countsST.delete.success += deletedCount.success; + } else { + countsST = { delete: { err: deletedCount.err, success: deletedCount.success } }; } } - // purge any remaining entries that are in GraphKB but have since been rejected/deleted by CIViC - const toDelete = await conn.getRecords({ - filters: { - AND: [ - { sourceId: Array.from(purgeableEvidenceItems) }, - { source: rid(source) }, - ], - }, - target: 'Statement', - }); + // Logging processing error cases to be reviewed, + // so a reviewer can decide if corresponding statements need to be deleted or not + logger.info(); + logger.info('***** Cases to be reviewed for deletion *****'); + logger.warn(`${casesToReview.size} Evidence Item(s) with processing errors leading to unmatched Statement(s)`); + casesToReview.forEach((v, k) => logger.info(`${k} -> ${JSON.stringify(v)}`)); + + // Logging Statement CRUD operations counts + if (countsST) { + logger.info(); + logger.info('***** GraphKB Statement records CRUD operations report: *****'); - try { - logger.warn(`Deleting ${toDelete.length} outdated CIViC statements from GraphKB`); - await Promise.all(toDelete.map(async statement => conn.deleteRecord( - 'Statement', rid(statement), - ))); - } catch (err) { - logger.error(err); + for (const op of Object.keys(countsST)) { + logger.info(`${op}: ${JSON.stringify(countsST[op])}`); + } } - logger.info(JSON.stringify(counts)); + // SAVING LOGGED ERRORS TO FILE + const errorFileContent = { + ...errorSourceIds, + validationErrors: validationErrorList, + }; const errorJson = `${errorLogPrefix}-civic.json`; - logger.info(`writing ${errorJson}`); - fs.writeFileSync(errorJson, JSON.stringify(errorList, null, 2)); + logger.info(); + logger.info(`***** Global report: *****\nwriting ${errorJson}`); + fs.writeFileSync(errorJson, JSON.stringify(errorFileContent, null, 2)); }; - module.exports = { - SOURCE_DEFN, - specs: { validateEvidenceSpec }, - translateRelevance, upload, }; diff --git a/src/civic/profile.js b/src/civic/profile.js new file mode 100644 index 00000000..4d6845d8 --- /dev/null +++ b/src/civic/profile.js @@ -0,0 +1,291 @@ +/** + * Introducing Molecular Profiles with CIViC GraphQL API v2.2.0 + * [EvidenceItem]--(many-to-one)--[MolecularProfile]--(many-to-many)--[Variant] + */ +const { error: { ErrorMixin } } = require('@bcgsc-pori/graphkb-parser'); + + +class NotImplementedError extends ErrorMixin { } +const MOLECULAR_PROFILE_CACHE = new Map(); + +/** + * Factory function returning a MolecularProfile object. + * The process() method allows for the process of Civic's Molecular Profiles + * After processing, the conditions property stores an array of GraphKB Statement's conditions + * + * @param {Object} molecularProfile a Molecular Profile segment from GraphQL query + * @returns {MolecularProfile} object whose conditions' property is an array of lists of conditions + */ +const MolecularProfile = (molecularProfile) => ({ + /* Combine new conditional variants with existing conditions */ + _combine({ arr1, arr2 }) { + const combinations = []; + + if (arr1[0].length === 0) { + return arr2; + } + if (arr2[0].length === 0) { + return arr1; + } + + arr1.forEach((e1) => { + arr2.forEach((e2) => { + e2.forEach((variant) => { + combinations.push([...e1, variant]); + }); + }); + }); + return combinations; + }, + /* Compile parsed block into array of conditions' arrays */ + _compile({ arr, op, part }) { + let conditions = []; + + switch (op) { + case 'AND': + arr.forEach((arrEl) => { + part.forEach((partEl) => { + conditions.push([...arrEl, ...partEl]); + }); + }); + break; + + case 'OR': + if (arr[0].length === 0) { + conditions = [...part]; + } else { + conditions = [...arr, ...part]; + } + break; + + default: + break; + } + return conditions; + }, + /* Desambiguation of variants with implicit 'or' in the name */ + _disambiguate() { + const newConditions = []; + + // For each set of conditions + this.conditions.forEach((condition) => { + const temp = []; + condition.forEach((variant) => { + temp.push( + // Split ambiguous variants into an array of 1 or more variant(s) + this._split(variant), + ); + }); + + // Combine variations into new condition + let newConditionSet; + + for (let i = 0; i < temp.length; i++) { + newConditionSet = this._combine({ arr1: newConditionSet || [[]], arr2: temp[i] }); + } + newConditions.push(...newConditionSet); + }); + + // Replace old conditions by new ones + this.conditions = [...newConditions]; + return this; + }, + /* Returns index of closing parenthesis for end of block */ + _end({ block, i, offset }) { + let count = 1, + j = 0; + + while (count > 0) { + j++; + + if (block[i + offset + j].text) { + switch (block[i + offset + j].text) { + case '(': + count++; + break; + + case ')': + count--; + break; + + default: + break; + } + } + } + return j; + }, + /* Returns true if parsedName contains NOT operator(s), otherwise false */ + _not(parsedName) { + for (let i = 0; i < parsedName.length; i++) { + if (parsedName[i].text && parsedName[i].text === 'NOT') { + return true; + } + } + return false; + }, + /* Parse block expression into array of conditions' arrays */ + _parse(block) { + let conditions = [[]], + offset = 0, + op = 'OR'; // Default operator + + for (let i = 0; i + offset < block.length; i++) { + const idx = i + offset; + + // If Variant + if (block[idx].id) { + // Add variant as a condition + conditions = this._compile({ + arr: conditions, + op, + part: [[block[idx].id]], + }); + continue; + } + + // If Nested block + if (block[idx].text && block[idx].text === '(') { + // Get end of block' index + const j = this._end({ + block, + i, + offset, + }); + // Recursively parse nested block + conditions = this._compile({ + arr: conditions, + op, + part: this._parse(block.slice(idx + 1, idx + j)), + }); + // New offset for rest of current block + offset += j; + continue; + } + + // If Operator + if (block[idx].text && ['AND', 'OR'].includes(block[idx].text)) { + op = block[idx].text; + continue; + } + } + return conditions; + }, + /* Splits variant's object into it's variations + * Ex. {name: 'Q157P/R'} --> [[ {name: 'Q157P'} ], [ {name: 'Q157R'} ]] */ + _split(variant) { + let orCombination; + + if (orCombination = /^([a-z]\d+)([a-z])\/([a-z])$/i.exec(variant.name)) { + const [, prefix, tail1, tail2] = orCombination; + return [ + [{ ...variant, name: `${prefix}${tail1}` }], + [{ ...variant, name: `${prefix}${tail2}` }], + ]; + } + return [[variant]]; + }, + /* Convert variant ids to variant objects */ + _variants() { + // Getting variants by ids from molecular profile's variants array + const variantsById = {}; + this.profile.variants.forEach((variant) => { + variantsById[variant.id] = variant; + }); + + // Refactoring conditions with variant objects + const newConditions = []; + this.conditions.forEach((condition) => { + newConditions.push(condition.map(id => variantsById[id])); + }); + + // Check if any missing variant object + newConditions.forEach((condition) => { + condition.forEach((variant) => { + if (!variant) { + const errMsg = `unable to process molecular profile with missing or misformatted variants (${this.profile.id || ''})`; + this.error = errMsg; + throw new Error(errMsg); + } + }); + }); + + // Replacing conditions with ones with variant's objects + this.conditions = newConditions; + return this; + }, + /* Corresponding GKB Statements' conditions (1 array per statement) */ + conditions: [[]], + /* Keep track of processing error */ + error: undefined, + /* Main object's method. Process expression into array of conditions' arrays */ + process() { + // Get Molecular Profile's expression (parsedName property) + const { parsedName } = this.profile; + + // Check for expression's format + if (!parsedName + || !Array.isArray(parsedName) + || parsedName.length === 0 + || typeof parsedName[0] !== 'object' + ) { + const errMsg = `unable to process molecular profile with missing or misformatted parsedName (${this.profile.id || ''})`; + this.error = errMsg; + throw new Error(errMsg); + } + // NOT operator not yet supported + if (this._not(parsedName)) { + const errMsg = `unable to process molecular profile with NOT operator (${this.profile.id || ''})`; + this.error = errMsg; + throw new NotImplementedError(errMsg); + } + // Filters out unwanted Feature info from expression + const filteredParsedName = parsedName.filter(el => el.__typename !== 'Feature'); + + // Parse expression into conditions + this.conditions = this._parse(filteredParsedName); + // Replace Variant's ids with corresponding Variant's objects + this._variants(); + // Disambiguate Variants' names with implicit 'or' + this._disambiguate(); + return this; + }, + /* CIViC Evidence Item's Molecular Profile segment */ + profile: molecularProfile || {}, +}); + +/** + * Processing a molecular profile expression while managing the cache + * + * @param {Object} molecularProfile a Molecular Profile segment from GraphQL query + * @returns {MolecularProfile} object whose conditions' property is an array of lists of conditions + */ +const processMolecularProfile = (molecularProfile) => { + let Mp = MOLECULAR_PROFILE_CACHE.get(molecularProfile.id); + + if (Mp) { + if (Mp.error) { + throw new Error( + `Molecular profile ${molecularProfile.id} already processed with error "${Mp.error}"`, + ); + } + return Mp; + } + Mp = MolecularProfile(molecularProfile); + + // Actually process the profile expression + try { + Mp.process(); + } catch (err) { + MOLECULAR_PROFILE_CACHE.set(molecularProfile.id, Mp); + throw err; + } + MOLECULAR_PROFILE_CACHE.set(molecularProfile.id, Mp); + + return Mp; +}; + +module.exports = { + MolecularProfile, + processMolecularProfile, +}; diff --git a/src/civic/publication.js b/src/civic/publication.js index 16a69934..644111ee 100644 --- a/src/civic/publication.js +++ b/src/civic/publication.js @@ -1,8 +1,18 @@ +const { error: { ErrorMixin } } = require('@bcgsc-pori/graphkb-parser'); + const _asco = require('../asco'); const _pubmed = require('../entrez/pubmed'); +class NotImplementedError extends ErrorMixin { } + + /** - * Check two strings are the same irrespective of casing, trailing periods and other formatting + * Check if two strings are the same irrespective of casing, + * trailing periods and other formatting + * + * @param {string} title1 a publication title + * @param {string} title2 a second publication title + * @returns {Boolean} whether both titles are matching or not */ const titlesMatch = (title1, title2) => { const title1Simple = title1.trim().toLowerCase().replace(/\.$/, '').replace(/<\/?(em|i|bold)>/g, ''); @@ -10,18 +20,25 @@ const titlesMatch = (title1, title2) => { return title1Simple === title2Simple; }; - /** - * Fetches the publication record either from pubmed or the ASCO abstract + * Fetches the publication record either from PubMed or the ASCO abstract * * @param {ApiConnection} conn graphkb API connector - * @param {object} rawRecord CIViC Evidence Item JSON record + * @param {object} rawRecord CIViC EvidenceItem record + * @returns {object} the publication record from GraphKB */ const getPublication = async (conn, rawRecord) => { + // Upload Publication to GraphKB FROM PUBMED if (rawRecord.source.sourceType === 'PUBMED') { const [publication] = await _pubmed.fetchAndLoadByIds(conn, [rawRecord.source.citationId]); + + if (!publication) { + throw Error(`PMID ${rawRecord.source.citationId} is not available`); + } return publication; } + + // Upload Publication to GraphKB FROM ASCO if (rawRecord.source.sourceType === 'ASCO') { const abstracts = await _asco.fetchAndLoadByIds(conn, [rawRecord.source.ascoAbstractId]); @@ -50,8 +67,16 @@ const getPublication = async (conn, rawRecord) => { } return abstracts[0]; } - throw Error(`unable to process non-pubmed/non-asco evidence type (${rawRecord.source.sourceType}) for evidence item (${rawRecord.id})`); -}; + // Upload Publication to GraphKB FROM ASH - No loader yet! + if (rawRecord.source.sourceType === 'ASH') { + // TODO: ASH loader. Only a handfull of cases though + } + throw new NotImplementedError(`unable to process non-pubmed/non-asco evidence type (${rawRecord.source.sourceType}) for evidence item (${rawRecord.id})`); +}; -module.exports = { getPublication, titlesMatch }; +module.exports = { + getPublication, + loadPubmedCache: _pubmed.preLoadCache, + titlesMatch, +}; diff --git a/src/civic/relevance.js b/src/civic/relevance.js new file mode 100644 index 00000000..3c1bff1f --- /dev/null +++ b/src/civic/relevance.js @@ -0,0 +1,358 @@ +const { error: { ErrorMixin } } = require('@bcgsc-pori/graphkb-parser'); + +class NotImplementedError extends ErrorMixin { } + +const RELEVANCE_CACHE = {}; + + +/** + * Extract the appropriate GraphKB relevance term from a CIViC evidence record + */ +const translateRelevance = (evidenceType, evidenceDirection, significance) => { + if (evidenceDirection === 'DOES_NOT_SUPPORT') { + switch (evidenceType) { + case 'DIAGNOSTIC': { + switch (significance) { + case 'NEGATIVE': { + break; + } + + case 'POSITIVE': { + break; + } + + default: { + break; + } + } + break; + } + + case 'FUNCTIONAL': { + switch (significance) { + case 'DOMINANT_NEGATIVE': { + return 'no dominant negative'; + } + + case 'GAIN_OF_FUNCTION': { + return 'no gain of function'; + } + + case 'LOSS_OF_FUNCTION': { + break; + } + + case 'NEOMORPHIC': { + return 'no neomorphic'; + } + + case 'UNALTERED_FUNCTION': { + break; + } + + case 'UNKNOWNED': { + break; + } + + default: { + break; + } + } + break; + } + + case 'ONCOGENIC': { + switch (significance) { + case 'ONCOGENICITY': { + return 'likely benign'; + } + + case 'PROTECTIVENESS': { + break; + } + + default: { + break; + } + } + break; + } + + case 'PREDICTIVE': { + switch (significance) { + case 'ADVERSE_RESPONSE': { + break; + } + + case 'NA': { + break; + } + + case 'REDUCED_SENSITIVITY': { + break; + } + + case 'RESISTANCE': { + return 'no resistance'; + } + + case 'SENSITIVITYRESPONSE': { + return 'no response'; + } + + default: { + break; + } + } + break; + } + + case 'PREDISPOSING': { + switch (significance) { + case 'PREDISPOSITION': { + break; + } + + case 'PROTECTIVENESS': { + break; + } + + default: { + break; + } + } + break; + } + + case 'PROGNOSTIC': { + switch (significance) { + case 'BETTER_OUTCOME': { + break; + } + + case 'NA': { + break; + } + + case 'POOR_OUTCOME': { + break; + } + + default: { + break; + } + } + break; + } + + default: { + break; + } + } + } else if (evidenceDirection === 'SUPPORTS') { + switch (evidenceType) { + case 'DIAGNOSTIC': { + switch (significance) { + case 'NEGATIVE': { + return 'opposes diagnosis'; + } + + case 'POSITIVE': { + return 'favours diagnosis'; + } + + default: { + break; + } + } + break; + } + + case 'FUNCTIONAL': { + switch (significance) { + case 'DOMINANT_NEGATIVE': { + return 'dominant negative'; + } + + case 'GAIN_OF_FUNCTION': { + return 'gain of function'; + } + + case 'LOSS_OF_FUNCTION': { + return 'loss of function'; + } + + case 'NEOMORPHIC': { + return 'neomorphic'; + } + + case 'UNALTERED_FUNCTION': { + return 'unaltered function'; + } + + case 'UNKNOWNED': { + break; + } + + default: { + break; + } + } + break; + } + + case 'ONCOGENIC': { + switch (significance) { + case 'ONCOGENICITY': { + return 'likely oncogenic'; + } + + case 'PROTECTIVENESS': { + break; + } + + default: { + break; + } + } + break; + } + + case 'PREDICTIVE': { + switch (significance) { + case 'ADVERSE_RESPONSE': { + return 'adverse response'; + } + + case 'NA': { + break; + } + + case 'REDUCED_SENSITIVITY': { + return 'reduced sensitivity'; + } + + case 'RESISTANCE': { + return 'resistance'; + } + + case 'SENSITIVITYRESPONSE': { + return 'sensitivity'; + } + + default: { + break; + } + } + break; + } + + case 'PREDISPOSING': { + switch (significance) { + case 'LIKELY_PATHOGENIC': { // Deprecated term + return 'likely pathogenic'; + } + + case 'PATHOGENIC': { // Deprecated term + return 'pathogenic'; + } + + case 'POSITIVE': { // Deprecated term + return 'predisposing'; + } + + case 'PREDISPOSITION': { + return 'likely predisposing'; + } + + case 'PROTECTIVENESS': { + return 'likely protective'; + } + + case 'UNCERTAIN_SIGNIFICANCE': { // Deprecated term + return 'likely predisposing'; + } + + default: { + break; + } + } + break; + } + + case 'PROGNOSTIC': { + switch (significance) { + case 'BETTER_OUTCOME': { + return 'favourable prognosis'; + } + + case 'NA': { + break; + } + + case 'POOR_OUTCOME': { + return 'unfavourable prognosis'; + } + + default: { + break; + } + } + break; + } + + default: { + break; + } + } + } + + // Addressing some NAs combinations + if (evidenceDirection === 'NA' && significance === 'NA') { // Deprecated term for both field + switch (evidenceType) { + case 'PREDISPOSING': { + return 'likely predisposing'; + } + + case 'ONCOGENIC': { + return 'likely oncogenic'; + } + + default: { + break; + } + } + } + + // If combination of evidenceDirection, evidenceType and significance not supported + throw new NotImplementedError( + `unable to process relevance (${JSON.stringify( + { evidenceDirection, evidenceType, significance }, + )})`, + ); +}; + +/** + * Convert the CIViC relevance types to GraphKB terms + */ +const getRelevance = async (conn, { rawRecord }) => { + // translate the type to a GraphKB vocabulary term + let relevance = translateRelevance( + rawRecord.evidenceType, + rawRecord.evidenceDirection, + rawRecord.significance, + ).toLowerCase(); + + if (RELEVANCE_CACHE[relevance] === undefined) { + relevance = await conn.getVocabularyTerm(relevance); + RELEVANCE_CACHE[relevance.name] = relevance; + } else { + relevance = RELEVANCE_CACHE[relevance]; + } + return relevance; +}; + +module.exports = { + getRelevance, + translateRelevance, +}; diff --git a/src/civic/specs.json b/src/civic/specs.json index 9a6782fe..740e28cc 100644 --- a/src/civic/specs.json +++ b/src/civic/specs.json @@ -1,31 +1,6 @@ { "EvidenceItem": { "properties": { - "clinicalSignificance": { - "enum": [ - "ADVERSE_RESPONSE", - "BENIGN", - "BETTER_OUTCOME", - "DOMINANT_NEGATIVE", - "GAIN_OF_FUNCTION", - "LIKELY_BENIGN", - "LIKELY_PATHOGENIC", - "LOSS_OF_FUNCTION", - "NA", - "NEGATIVE", - "NEOMORPHIC", - "PATHOGENIC", - "POOR_OUTCOME", - "POSITIVE", - "REDUCED_SENSITIVITY", - "RESISTANCE", - "SENSITIVITYRESPONSE", - "UNALTERED_FUNCTION", - "UNCERTAIN_SIGNIFICANCE", - "UNKNOWN", - null - ] - }, "description": { "type": [ "null", @@ -35,6 +10,12 @@ "disease": { "properties": { "doid": { + "type": [ + "null", + "string" + ] + }, + "id": { "type": [ "null", "number" @@ -52,46 +33,6 @@ "object" ] }, - "drugInteractionType": { - "enum": [ - "COMBINATION", - "SEQUENTIAL", - "SUBSTITUTES", - null - ] - }, - "drugs": { - "items": { - "properties": { - "id": { - "type": [ - "null", - "number" - ] - }, - "name": { - "type": [ - "null", - "string" - ] - }, - "ncitId": { - "type": [ - "null", - "string" - ] - } - }, - "type": [ - "null", - "object" - ] - }, - "type": [ - "array", - "null" - ] - }, "evidenceDirection": { "enum": [ "DOES_NOT_SUPPORT", @@ -127,31 +68,105 @@ null ] }, - "gene": { + "id": { + "type": "number" + }, + "molecularProfile": { "properties": { "id": { + "type": "number" + }, + "name": { "type": [ "null", - "number" + "string" ] }, - "name": { + "parsedName": { + "items": { + "properties": { + "__typename": { + "type": "string" + }, + "id": { + "type": "number" + }, + "text": { + "type": "string" + } + }, + "required":[ + "__typename" + ], + "type": "object" + }, + "type": "array" + }, + "rawName": { "type": [ "null", "string" ] + }, + "variants": { + "items": { + "properties": { + "feature": { + "properties": { + "featureInstance": { + "properties": { + "__typename": { + "type": "string" + }, + "entrezId": { + "type": "number" + }, + "name": { + "type": "string" + } + }, + "required":[ + "__typename", + "entrezId", + "name" + ], + "type": "object" + } + }, + "required": [ + "featureInstance" + ], + "type": "object" + }, + "id": { + "type": "number" + }, + "name": { + "type": "string" + } + }, + "required": [ + "feature", + "id", + "name" + ], + "type": [ + "object" + ] + }, + "type": [ + "array" + ] } }, - "type": [ - "null", - "object" - ] - }, - "id": { - "type": [ - "null", - "number" - ] + "required": [ + "id", + "name", + "parsedName", + "rawName", + "variants" + ], + "type": "object" }, "phenotypes": { "items": { @@ -179,6 +194,34 @@ "null" ] }, + "significance": { + "enum": [ + "ADVERSE_RESPONSE", + "BENIGN", + "BETTER_OUTCOME", + "DOMINANT_NEGATIVE", + "GAIN_OF_FUNCTION", + "LIKELY_BENIGN", + "LIKELY_PATHOGENIC", + "LOSS_OF_FUNCTION", + "NA", + "NEGATIVE", + "NEOMORPHIC", + "ONCOGENICITY", + "PATHOGENIC", + "POOR_OUTCOME", + "POSITIVE", + "PREDISPOSITION", + "PROTECTIVENESS", + "REDUCED_SENSITIVITY", + "RESISTANCE", + "SENSITIVITYRESPONSE", + "UNALTERED_FUNCTION", + "UNCERTAIN_SIGNIFICANCE", + "UNKNOWN", + null + ] + }, "source": { "properties": { "ascoAbstractId": { @@ -190,7 +233,7 @@ "citationId": { "type": [ "null", - "number" + "string" ] }, "name": { @@ -208,6 +251,7 @@ "sourceType": { "enum": [ "ASCO", + "ASH", "PUBMED", null ] @@ -229,50 +273,63 @@ null ] }, - "variant": { - "properties": { - "gene": { - "properties": { - "entrezId": { - "type": [ - "null", - "number" - ] - }, - "name": { - "type": [ - "null", - "string" - ] - } + "therapies": { + "items": { + "properties": { + "id": { + "type": [ + "null", + "number" + ] }, - "type": [ - "null", - "object" - ] - }, - "id": { - "type": [ - "null", - "number" - ] + "name": { + "type": [ + "null", + "string" + ] + }, + "ncitId": { + "type": [ + "null", + "string" + ] + } }, - "name": { - "type": [ - "null", - "string" - ] - } + "type": [ + "null", + "object" + ] }, "type": [ - "null", - "object" + "array", + "null" + ] + }, + "therapyInteractionType": { + "enum": [ + "COMBINATION", + "SEQUENTIAL", + "SUBSTITUTES", + null ] } }, - "type": [ - "null", - "object" - ] + "required":[ + "description", + "disease", + "evidenceDirection", + "evidenceLevel", + "evidenceRating", + "evidenceType", + "id", + "molecularProfile", + "phenotypes", + "significance", + "source", + "status", + "therapies", + "therapyInteractionType" + ], + "type": "object" } } diff --git a/src/civic/statement.js b/src/civic/statement.js new file mode 100644 index 00000000..e4049bcf --- /dev/null +++ b/src/civic/statement.js @@ -0,0 +1,292 @@ +const _ = require('lodash'); + +const { logger } = require('../logging'); + + +/** + * Evaluate if two statement's content can be matched to one another. + * Used to map each EvidenceLevel's combination to its corresponding GraphKB statement + * + * @param {object} fromCivic new content from CIViC + * @param {object} fromGkb actual content from GraphKB + * @returns {boolean} whether both contents are matching or not + */ +const isMatching = ({ fromCivic, fromGkb, p = ['conditions', 'subject'] }) => ( + JSON.stringify(_.pick(fromCivic, ...p)) === JSON.stringify(_.pick(fromGkb, ...p)) +); + +/** + * Evaluate if a statement needs to be updated + * when compared to its matching EvidenceLevel's combination + * + * @param {object} param0 + * @param {object} param0.fromCivic new content from CIViC + * @param {object} param0.fromGkb actual content from GraphKB + * @returns {boolean} whether the GraphKB record needs to be updated or not + */ +const needsUpdate = ({ fromCivic, fromGkb }) => { + const isEqual = JSON.stringify(fromCivic) === JSON.stringify(_.omit(fromGkb, ['@rid'])); + + // Logging details if not equal + if (!isEqual) { + const updatedFields = []; + + for (const [key, value] of Object.entries(fromCivic)) { + if (JSON.stringify(value) !== JSON.stringify(fromGkb[key])) { + updatedFields.push(key); + } + } + logger.info(`Update needed on ${updatedFields.toString()}`); + } + + return !isEqual; +}; + +/** + * Given an array of content from civic and an array of actual statements from GraphKG, + * match corresponding content together + * + * @param {object} param0 + * @param {object[]} param0.allFromCivic array of new content from CIViC + * @param {object[]} param0.allFromGkb array of actual content from GraphKB + * @param boolean} param0.matchingOnSubjectAlone if additional matching on subject alone + * @param boolean} param0.matchingWithoutComparing if random matching with remaining records + * @returns {object} content of records to create, update and delete in GrpahKB + */ +const contentMatching = ({ + allFromCivic, + allFromGkb, + matchingOnSubjectAlone = true, + matchingWithoutComparing = true, +}) => { + const records = { + toCreate: [], // Array of content from CIViC to create as GraphKB statements + toDelete: [], // Array of GraphKB statements to delete + toUpdate: [], /* Array of CIViC-GraphKB pairs of content for statement update + Note: statement will be updated only if needed */ + }; + + /* + MATCHING ONE TO ONE + + Will automatically be submitted for update, without deletion/creation + */ + + if (allFromCivic.length === 1 && allFromGkb.length === 1) { + records.toUpdate.push({ fromCivic: allFromCivic[0], fromGkb: allFromGkb[0] }); + return records; + } + + /* + MATCHING ON CONDITIONS AND SUBJECT + */ + + const remainingFromGkb = [...allFromGkb]; + allFromCivic.forEach(el => { + let matched = false; + + for (let i = 0; i < remainingFromGkb.length; i++) { + // matching on conditions and subject (default) + if (isMatching({ + fromCivic: el, + fromGkb: remainingFromGkb[i], + })) { + records.toUpdate.push({ + fromCivic: el, + fromGkb: remainingFromGkb[i], + }); + remainingFromGkb.splice(i, 1); + matched = true; + break; + } + } + + if (!matched) { + records.toCreate.push(el); + } + }); + records.toDelete = [...remainingFromGkb]; + + /* + MATCHING ON SUBJECT ALONE + */ + if (!matchingOnSubjectAlone) { return records; } + + let numUnmatched = Math.min( + records.toCreate.length, + records.toDelete.length, + ); + + if (numUnmatched > 0) { + const remainingToCreate = []; + + for (let j = 0; j < records.toCreate.length; j++) { + let matched = false; + + for (let i = 0; i < records.toDelete.length; i++) { + // matching on subject + if (isMatching({ + fromCivic: records.toCreate[j], + fromGkb: records.toDelete[i], + p: ['subject'], + })) { + records.toUpdate.push({ + fromCivic: records.toCreate[j], + fromGkb: records.toDelete[i], + }); + records.toDelete.splice(i, 1); + matched = true; + break; + } + } + + if (!matched) { + remainingToCreate.push(records.toCreate[j]); + } + } + records.toCreate = [...remainingToCreate]; + } + + /* + ARTIFICIAL MATCHING WITHOUT COMPARISON + + In order to reduce unnecessary create/delete statements, + artificially match pairs until only some records.toCreate record(s) remains + or some records.toDelete record(s) remains. + */ + if (!matchingWithoutComparing) { return records; } + + numUnmatched = Math.min( + records.toCreate.length, + records.toDelete.length, + ); + + // Randomly match remaining content + if (numUnmatched > 0) { + for (let i = 0; i < numUnmatched; i++) { + // 'Artificial' pairing + records.toUpdate.push( + { fromCivic: records.toCreate[i], fromGkb: records.toDelete[i] }, + ); + } + // Remove from records.toCreate and records.toDelete + records.toCreate.splice(0, numUnmatched); + records.toDelete.splice(0, numUnmatched); + } + + return records; +}; + +/** + * Given content from CIViC, try to create the GraphKB record + * + * @param {ApiConnection} conn the API connection object for GraphKB + * @param {object} param1 + * @param {object[]} param1.fromCivic new content from CIViC + * @returns {object} a count object for error and success + */ +const createStatement = async (conn, { fromCivic }) => { + const counts = { err: 0, success: 0 }; + + try { + await conn.addRecord({ content: fromCivic, target: 'Statement' }); + counts.success++; + } catch (err) { + logger.error(err); + counts.err++; + } + + return counts; +}; + +/** + * Given content from CIViC and a corresponding GraphKB Statement rid, + * try to update the GraphKB record + * + * @param {ApiConnection} conn the API connection object for GraphKB + * @param {object} param1 + * @param {object[]} param1.fromCivic new content from CIViC + * @param {object[]} param1.fromGkb actual content from GraphKB + * @returns {object} a count object for error and success + */ +const updateStatement = async (conn, { fromCivic, fromGkb }) => { + const counts = { err: 0, success: 0 }; + + try { + await conn.addRecord({ + content: fromCivic, + existsOk: true, + fetchConditions: { + // Since CIViC content has already been matched + // to its corresponding GraphKB statement + '@rid': fromGkb['@rid'], + }, + target: 'Statement', + upsert: true, + }); + counts.success++; + } catch (err) { + logger.error(err); + counts.err++; + } + + return counts; +}; + +/** + * Soft-delete GraphKB Statements from either an array of Statement's RIDs + * or an array of sourceIds and its corresponding source + * + * @param {ApiConnection} conn the api connection object for GraphKB + * @param {object} param1 + * @param {?string[]} param1.rids an array of Statement's RIDs + * @param {string} param1.source the source RID + * @param {string[]} param1.sourceIds an array of sourceIds + * @returns {object} a count object for error and success + */ +const deleteStatements = async (conn, { rids = [], source, sourceIds }) => { + const counts = { err: 0, success: 0 }; + + // Get rids to delete if none provided + if (rids.length === 0) { + logger.info('Loading corresponding GraphKB statement RIDs to delete'); + const records = await conn.getRecords({ + filters: { + AND: [ + { sourceId: sourceIds }, + { source }, + ], + }, + target: 'Statement', + }); + rids.push(...records.map( + (el) => el['@rid'], + )); + logger.info(`${rids.length} RIDs found`); + } + + // Delete statements + logger.info(`Deleting ${rids.length} statement(s)...`); + logger.info(rids); + + for (const r of rids) { + try { + await conn.deleteRecord('Statement', r); + counts.success++; + } catch (err) { + logger.error(err); + counts.err++; + } + } + + return counts; +}; + +module.exports = { + contentMatching, + createStatement, + deleteStatements, + isMatching, + needsUpdate, + updateStatement, +}; diff --git a/src/civic/therapy.js b/src/civic/therapy.js new file mode 100644 index 00000000..021f88ff --- /dev/null +++ b/src/civic/therapy.js @@ -0,0 +1,200 @@ +const { logger } = require('../logging'); +const { ncit: NCIT_SOURCE_DEFN } = require('../sources'); +const { orderPreferredOntologyTerms, rid } = require('../graphkb'); + + +/** + * Given a CIViC EvidenceItem record, + * compile a list of its therapies into a list of combination of therapies, and + * returns modified 'therapies' & 'therapyInteractionType' properties + * + * record.therapies will be transformed into: + * - a list of 1 list of 1-or-many therapies ('COMBINATION' || 'SEQUENTIAL'), or + * - a list of 1-or-many lists of 1 therapy ('SUBSTITUTES'), or + * - a list of 1 null + * + * @param {object} evidenceItem the original CIViC EvidenceItem + * @returns {object} the modified EvidenceItem + */ +const resolveTherapies = (evidenceItem) => { + const record = JSON.parse(JSON.stringify(evidenceItem)); // Deep copy + + // No therapy + if (record.therapies === null || record.therapies.length === 0) { + record.therapies = [null]; + return record; + } + + // One or more therapies + if (record.therapies.length === 1 || record.therapyInteractionType === 'SUBSTITUTES') { + record.therapies = record.therapies.map(therapy => [therapy]); + record.therapyInteractionType = null; + } else if ( + record.therapyInteractionType === 'COMBINATION' + || record.therapyInteractionType === 'SEQUENTIAL' + ) { + record.therapies = [record.therapies]; + } else { + logger.error(`(evidence: ${record.id}) unsupported therapy interaction type (${record.therapyInteractionType}) for a multiple therapy (${record.therapies.length}) statement`); + throw new Error('Did not find unique record'); + } + + // Since duplicates can occure (from civic !?), lets remove them + // Need to strignify/parse since we're comparing arrays of objects + const unique = new Set(); + record.therapies.forEach(therapy => unique.add(JSON.stringify(therapy))); + record.therapies = []; + unique.forEach(therapy => record.therapies.push(JSON.parse(therapy))); + + return record; +}; + +/** + * Given a Therapy record from CIViC, + * returns a Therapy record from GraphKB + * + * @param {ApiConnection} conn the API connection object for GraphKB + * @param {object} therapyRecord a therapy from CIViC + * @returns {object} Therapy record from GraphKB + */ +const getTherapy = async (conn, therapyRecord) => { + const name = therapyRecord.name.toLowerCase().trim(); + const ncitId = therapyRecord.ncitId && typeof therapyRecord.ncitId === 'string' + ? therapyRecord.ncitId.toLowerCase().trim() + : therapyRecord.ncitId; + + if (ncitId) { + // Trying with the ncitId and the name + try { + return await conn.getUniqueRecordBy({ + filters: [ + { source: { filters: { name: NCIT_SOURCE_DEFN.name }, target: 'Source' } }, + { sourceId: ncitId }, + { name }, + ], + sort: orderPreferredOntologyTerms, + target: 'Therapy', + }); + } catch (err) { + logger.warn(`Failed to fetch therapy with NCIt id (${ncitId}) & name (${therapyRecord.name}) from graphkb`); + } + + // Trying with the ncitId only + // Choosing the most recently created one + try { + const matchingTherapies = await conn.getRecords({ + filters: { + AND: [ + { source: { filters: { name: NCIT_SOURCE_DEFN.name }, target: 'Source' } }, + { sourceId: ncitId }, + ], + }, + target: 'Therapy', + }); + // In-place sorting + matchingTherapies.sort((a, b) => b.createdAt - a.createdAt); + // returning 1st one (latest created) + return matchingTherapies[0]; + } catch (err) { + logger.error(`Failed to fetch therapy with NCIt id (${ncitId}) from graphkb`); + throw err; + } + } + + let originalError; + + // Trying instead with the name + // Using the getTherapy method from the connection object + try { + // With the name as-is first + return await conn.getTherapy(name); + } catch (err) { + originalError = err; + } + + try { + // Then with the name parsed + const match = /^\s*(\S+)\s*\([^)]+\)$/.exec(name); + + if (match) { + return await conn.getTherapy(match[1]); + } + } catch (err) { } + + // Logging errors + throw originalError; +}; + +/** + * Given a list of CIViC Therapy Records, + * + * (If one therapy) + * returns the corresponding Therapy record from GraphKB + * + * (If a combination of therapies) + * will add a therapy combination if there is not an existing record, + * will link the therapy combination to its individual elements with 'ElementOf' edges, then + * returns the corresponding Therapy record from GraphKB + * + * @param {ApiConnection} conn the API connection object for GraphKB + * @param {string} sourceRid + * @param {object[]} therapiesRecords + * @param {string} combinationType + * @returns {object} the corresponding Therapy record from GraphKB + */ +const addOrFetchTherapy = async (conn, sourceRid, therapiesRecords, combinationType) => { + /* ONE OR NO THERAPY */ + + if (therapiesRecords.length === 0) { + return null; + } + if (therapiesRecords.length === 1) { + if (therapiesRecords[0] === null) { + return null; + } + // Get the corresponding Therapy record from GraphKB + return getTherapy(conn, therapiesRecords[0]); + } + + /* COMBINATION OF THERAPIES */ + + // For each therapy, get the corresponding Therapy record from GraphKB + const therapies = await Promise.all( + therapiesRecords.map( + async therapy => getTherapy(conn, therapy), + ), + ); + // concatenating sourceIds and names + const sourceId = therapies.map(e => e.sourceId).sort().join(' + '); + const name = therapies.map(e => e.name).sort().join(' + '); + + // Add a Therapy Vertice for the combined therapies + const combinedTherapy = await conn.addRecord({ + content: { + combinationType, name, source: sourceRid, sourceId, + }, + existsOk: true, + target: 'Therapy', + }); + + // Add ElementOf Edges between corresponding records + for (const therapy of therapies) { + await conn.addRecord({ + content: { + in: rid(combinedTherapy), + out: rid(therapy), + source: sourceRid, + }, + existsOk: true, + target: 'ElementOf', + }); + } + + return combinedTherapy; +}; + +module.exports = { + addOrFetchTherapy, + getTherapy, + resolveTherapies, +}; diff --git a/src/civic/variant.js b/src/civic/variant.js index 2ae3c360..98ebee67 100644 --- a/src/civic/variant.js +++ b/src/civic/variant.js @@ -1,16 +1,13 @@ const kbParser = require('@bcgsc-pori/graphkb-parser'); - -const { error: { ParsingError } } = kbParser; -const { - rid, -} = require('../graphkb'); +const { rid } = require('../graphkb'); const _entrezGene = require('../entrez/gene'); const _snp = require('../entrez/snp'); +const { civic: SOURCE_DEFN } = require('../sources'); -const { - civic: SOURCE_DEFN, -} = require('../sources'); +const { error: { ErrorMixin, ParsingError } } = kbParser; +class NotImplementedError extends ErrorMixin { } +const VARIANT_CACHE = new Map(); // based on discussion with cam here: https://www.bcgsc.ca/jira/browse/KBDEV-844 const SUBS = { @@ -35,7 +32,13 @@ const SUBS = { 'p26.3-25.3 11mb del': 'y.p26.3_p25.3del', }; - +/** + * Compares two gene names together for equality + * + * @param {string} gene1 a gene name + * @param {string} gene2 a second gene name + * @returns {boolean} whether the genes names are equal or not + */ const compareGeneNames = (gene1, gene2) => { if (['abl1', 'abl'].includes(gene1.toLowerCase()) && ['abl1', 'abl'].includes(gene2.toLowerCase())) { return true; @@ -46,7 +49,14 @@ const compareGeneNames = (gene1, gene2) => { }; /** - * Given a CIViC Variant record entrez information and name, normalize into a set of graphkb-style variants + * Given a CIViC Variant record entrez information and name, + * normalize into a set of graphkb-style variants + * + * @param {object} param0 + * @param {string} param0.name + * @param {string} param0.entrezId + * @param {string} param0.entrezName + * @returns {object} */ const normalizeVariantRecord = ({ name: rawName, entrezId, entrezName: rawEntrezName, @@ -229,12 +239,13 @@ const normalizeVariantRecord = ({ }; /** - * Given some normalized variant record from CIViC load into graphkb, create links and - * return the record + * Given some normalized variant record from CIViC, + * load into graphkb, create links and return the record * * @param {ApiConnection} conn the connection to GraphKB * @param {Object} normalizedVariant the normalized variant record * @param {Object} feature the gene feature already grabbed from GraphKB + * @returns {object[]} */ const uploadNormalizedVariant = async (conn, normalizedVariant, feature) => { let result; @@ -275,6 +286,10 @@ const uploadNormalizedVariant = async (conn, normalizedVariant, feature) => { reference2 = feature; // fetch reference1 [reference1] = await _entrezGene.fetchAndLoadBySymbol(conn, normalizedVariant.reference1.name); + + if (!reference1) { + throw new Error(`Gene name not found in NCBI's Entrez gene database (${normalizedVariant.reference1.name}})`); + } } else if (normalizedVariant.reference1.sourceId !== feature.sourceId) { throw new ParsingError(`Feature ID input (${feature.sourceId}) does not match the linked gene IDs (${normalizedVariant.reference1.sourceId},${normalizedVariant.reference2.sourceId})`); } else { @@ -282,6 +297,7 @@ const uploadNormalizedVariant = async (conn, normalizedVariant, feature) => { [reference2] = await _entrezGene.fetchAndLoadBySymbol(conn, normalizedVariant.reference2.name); } } + content.reference1 = rid(reference1); if (reference2) { @@ -320,27 +336,71 @@ const uploadNormalizedVariant = async (conn, normalizedVariant, feature) => { return result; }; - /** * Given some variant record and a feature, process the variant and return a GraphKB equivalent + * + * @param {ApiConnection} conn the connection to GraphKB + * @param {Object} civicVariantRecord the raw variant record from CIViC + * @param {Object} feature the gene feature already grabbed from GraphKB + * @returns {object[]} */ const processVariantRecord = async (conn, civicVariantRecord, feature) => { - const variants = normalizeVariantRecord({ - entrezId: civicVariantRecord.gene.entrezId, - entrezName: civicVariantRecord.gene.name, + const { feature: { featureInstance } } = civicVariantRecord; + let entrezId, + entrezName; + + // featureInstance + if (featureInstance.__typename === 'Gene') { + entrezId = featureInstance.entrezId; + entrezName = featureInstance.name; + } else if (featureInstance.__typename === 'Factor') { + // TODO: Deal with __typename === 'Factor' + // No actual case as April 22nd, 2024 + throw new NotImplementedError( + 'unable to process variant\'s feature of type Factor', + ); + } + + // Raw variant from CIViC to normalize & upload to GraphKB if needed + const rawVariant = { + entrezId, + entrezName, name: civicVariantRecord.name, - }); + }; + + // Trying cache first + const fromCache = VARIANT_CACHE.get(JSON.stringify(rawVariant)); + + if (fromCache) { + if (fromCache.err) { + throw new Error('Variant record previously processed with errors'); + } + if (fromCache.result) { + return fromCache.result; + } + } const result = []; - for (const normalizedVariant of variants) { - result.push(await uploadNormalizedVariant(conn, normalizedVariant, feature)); + try { + // Normalizing + const variants = normalizeVariantRecord(rawVariant); + + // Uploading + for (const normalizedVariant of variants) { + result.push(await uploadNormalizedVariant(conn, normalizedVariant, feature)); + } + } catch (err) { + VARIANT_CACHE.set(JSON.stringify(rawVariant), { err }); } + + VARIANT_CACHE.set(JSON.stringify(rawVariant), { result }); return result; }; - module.exports = { + compareGeneNames, normalizeVariantRecord, processVariantRecord, + uploadNormalizedVariant, }; diff --git a/src/clinicaltrialsgov/README.md b/src/clinicaltrialsgov/README.md index 675446d4..9cd4b0e5 100644 --- a/src/clinicaltrialsgov/README.md +++ b/src/clinicaltrialsgov/README.md @@ -4,21 +4,15 @@ This module loads clinical trials data into GraphKB from [https://www.clinicaltr > :warning: Since this loader produces statements, ontology and vocabulary data should be loaded first -## Multiple XML Files - -Loads Trial records from XML files. See: [https://clinicaltrials.gov/ct2/resources/download#DownloadMultipleRecords](https://clinicaltrials.gov/ct2/resources/download#DownloadMultipleRecords) +Uses REST API to load clinical trials data. ```bash -wget https://clinicaltrials.gov/AllPublicXML.zip -unzip AllPublicXML.zip +node bin/load.js clinicaltrialsgov ``` -Then you can load these by pointing directly to the sub-folders - +By default this loader loads all studies that related to cancer, which will be a huge number of records. +Using `--days` can load the new and existing studies added or modified (last update posted) in the last # of days. ```bash -for folder in AllPublicXML/*; -do - echo "Loading folder: $folder" - node bin/clinicaltrialsgov.js --dir $folder -done +node bin/load.js clinicaltrialsgov --days 7 ``` +Loading the studies added or modified in the last week. diff --git a/src/clinicaltrialsgov/index.js b/src/clinicaltrialsgov/index.js index 7fa093ce..099271de 100644 --- a/src/clinicaltrialsgov/index.js +++ b/src/clinicaltrialsgov/index.js @@ -1,21 +1,10 @@ /** * Module to import clinical trials data exported from clinicaltrials.gov - * - * 1. Perform a search on their site, for example https://clinicaltrials.gov/ct2/results?cond=Cancer&cntry=CA&Search=Apply&recrs=b&recrs=a&age_v=&gndr=&type=Intr&rslt= - * 2. Click their Download link/Button - * 3. Adjust the settings in the Pop up dialog (Include all studies, all columns, and export as XML) - * 4. Download and save the file - * 5. Upload the file to GraphKB using this module - * * @module importer/clinicaltrialsgov */ -const path = require('path'); const Ajv = require('ajv'); -const fs = require('fs'); const { - loadXmlToJson, - parseXmlToJson, checkSpec, requestWithRetry, } = require('../util'); @@ -25,89 +14,77 @@ const { } = require('../graphkb'); const { logger } = require('../logging'); const { clinicalTrialsGov: SOURCE_DEFN } = require('../sources'); -const { api: apiSpec, rss: rssSpec } = require('./specs.json'); +const { studies: studiesSpecs } = require('./specs.json'); -const BASE_URL = 'https://clinicaltrials.gov/ct2/show'; -const RSS_URL = 'https://clinicaltrials.gov/ct2/results/rss.xml'; +const BASE_URL = 'https://clinicaltrials.gov/api/v2/studies'; const CACHE = {}; const ajv = new Ajv(); -const validateAPITrialRecord = ajv.compile(apiSpec); -const validateRssFeed = ajv.compile(rssSpec); - - -const standardizeDate = (dateString) => { - const dateObj = new Date(Date.parse(dateString)); - - if (Number.isNaN(dateObj.getTime())) { - throw new Error(`unable to standardize input date (${dateString})`); - } - const month = dateObj.getMonth() + 1 < 10 - ? `0${dateObj.getMonth() + 1}` - : dateObj.getMonth() + 1; - const date = dateObj.getDate() < 10 - ? `0${dateObj.getDate()}` - : dateObj.getDate(); - return `${dateObj.getFullYear()}-${month}-${date}`; -}; +const validateAPITrialRecord = ajv.compile(studiesSpecs); /** * Given some records from the API, convert its form to a standard represention */ const convertAPIRecord = (rawRecord) => { - checkSpec(validateAPITrialRecord, rawRecord, rec => rec.clinical_study.id_info[0].nct_id[0]); + checkSpec(validateAPITrialRecord, rawRecord, rec => rec.protocolSection.identificationModule.nctId); - const { clinical_study: record } = rawRecord; + const { protocolSection: record } = rawRecord; let startDate, completionDate; try { - startDate = standardizeDate(record.start_date[0]._ || record.start_date[0]); + startDate = record.statusModule.startDateStruct.date; } catch (err) {} try { - completionDate = standardizeDate( - record.completion_date[0]._ || record.completion_date[0], - ); + completionDate = record.statusModule.completionDateStruct.date; } catch (err) {} - const [title] = record.official_title || record.brief_title; + const title = record.identificationModule.officialTitle || record.identificationModule.briefTitle; + + const { nctId } = record.identificationModule; + const url = `${BASE_URL}/${nctId}`; const content = { completionDate, - diseases: record.condition, + diseases: record.conditionsModule.conditions, displayName: title, drugs: [], locations: [], name: title, - recruitmentStatus: record.overall_status[0], - sourceId: record.id_info[0].nct_id[0], - sourceIdVersion: standardizeDate(record.last_update_posted[0]._), + recruitmentStatus: record.statusModule.overallStatus, + sourceId: nctId, + sourceIdVersion: record.statusModule.lastUpdatePostDateStruct.date, startDate, - url: record.required_header[0].url[0], + url, }; - if (record.phase) { - content.phases = record.phase; + if (record.designModule.phases) { + content.phases = record.designModule.phases; } - for (const { intervention_name: [name], intervention_type: [type] } of record.intervention || []) { + for (const { name, type } of record.armsInterventionsModule.interventions || []) { if (type.toLowerCase() === 'drug' || type.toLowerCase() === 'biological') { content.drugs.push(name); } } - for (const location of record.location || []) { - const { facility: [{ address }] } = location; - - if (!address) { - continue; + if (record.contactsLocationsModule) { + for (const { country, city } of record.contactsLocationsModule.locations || []) { + if (city && country) { + content.locations.push({ city: city.toLowerCase(), country: country.toLowerCase() }); + } + if (city && !country) { + content.locations.push({ city: city.toLowerCase() }); + } + if (!city && country) { + content.locations.push({ country: country.toLowerCase() }); + } } - const [{ country: [country], city: [city] }] = address; - content.locations.push({ city: city.toLowerCase(), country: country.toLowerCase() }); } + return content; }; @@ -119,8 +96,8 @@ const processPhases = (phaseList) => { const cleanedPhaseList = raw.trim().toLowerCase().replace(/\bn\/a\b/, '').split(/[,/]/); for (const phase of cleanedPhaseList) { - if (phase !== '' && phase !== 'not applicable') { - const match = /^(early )?phase (\d+)$/.exec(phase); + if (phase !== '' && phase !== 'na' && phase !== 'ph') { + const match = /^(early_)?phase(\d+)$/.exec(phase); if (!match) { throw new Error(`unrecognized phase description (${phase})`); @@ -134,11 +111,11 @@ const processPhases = (phaseList) => { /** - * Process the XML trial record. Attempt to link the drug and/or disease information + * Process the record. Attempt to link the drug and/or disease information * * @param {object} opt * @param {ApiConnection} opt.conn the GraphKB connection object - * @param {object} opt.record the XML record (pre-parsed into JSON) + * @param {object} opt.record the record (pre-parsed into JSON) * @param {object|string} opt.source the 'source' record for clinicaltrials.gov * * @todo: handle updates to existing clinical trial records @@ -149,13 +126,18 @@ const processRecord = async ({ const content = { displayName: record.displayName, name: record.name, - recruitmentStatus: record.recruitmentStatus, + recruitmentStatus: record.recruitmentStatus.replace(/_/g, ' '), source: rid(source), sourceId: record.sourceId, sourceIdVersion: record.sourceIdVersion, url: record.url, }; + // temperory mapping to avoid schema change + if (content.recruitmentStatus && content.recruitmentStatus.toLowerCase() === 'active not recruiting') { + content.recruitmentStatus = 'active, not recruiting'; + } + if (content.recruitmentStatus && content.recruitmentStatus.toLowerCase() === 'unknown status') { content.recruitmentStatus = 'unknown'; } @@ -175,24 +157,25 @@ const processRecord = async ({ consensusCity; for (const { city, country } of record.locations) { - if (consensusCountry) { + if (country && consensusCountry) { if (consensusCountry !== country.toLowerCase()) { consensusCountry = null; consensusCity = null; break; } - } else { + } else if (country) { consensusCountry = country.toLowerCase(); } - if (consensusCity !== undefined) { + if (city && consensusCity) { if (consensusCity !== city.toLowerCase()) { consensusCity = null; } - } else { + } else if (city) { consensusCity = city.toLowerCase(); } } + if (consensusCountry) { content.country = consensusCountry; @@ -264,7 +247,7 @@ const processRecord = async ({ /** * Given some NCT ID, fetch and load the corresponding clinical trial information * - * https://clinicaltrials.gov/ct2/show/NCT03478891?displayxml=true + * https://clinicaltrials.gov/api/v2/studies/NCT03478891 */ const fetchAndLoadById = async (conn, nctID, { upsert = false } = {}) => { const url = `${BASE_URL}/${nctID}`; @@ -290,14 +273,11 @@ const fetchAndLoadById = async (conn, nctID, { upsert = false } = {}) => { } catch (err) {} logger.info(`loading: ${url}`); // fetch from the external api - const resp = await requestWithRetry({ - headers: { Accept: 'application/xml' }, + const result = await requestWithRetry({ json: true, method: 'GET', - qs: { displayxml: true }, uri: url, }); - const result = await parseXmlToJson(resp); // get or add the source if (!CACHE.source) { @@ -313,83 +293,81 @@ const fetchAndLoadById = async (conn, nctID, { upsert = false } = {}) => { return trial; }; +const formatDate = (date) => `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`; + /** - * Uploads a file exported from clinicaltrials.gov as XML - * @param {object} opt - * @param {ApiConnection} opt.conn the GraphKB connection object - * @param {string} opt.filename the path to the XML export + * Loading all clinical trials related to cancer */ -const uploadFiles = async ({ conn, files }) => { +const upload = async ({ conn, maxRecords, days }) => { const source = await conn.addSource(SOURCE_DEFN); - logger.info(`loading ${files.length} records`); + let options, + optionsWithToken; + + if (days) { + const startDate = new Date(Date.now() - days * 24 * 60 * 60 * 1000); + options = { 'query.term': `AREA[LastUpdatePostDate]RANGE[${formatDate(startDate)},MAX]` }; + logger.info(`loading records updated from ${formatDate(startDate)} to ${formatDate(new Date())}`); + } + const counts = { error: 0, success: 0, }; - for (const filepath of files) { - const filename = path.basename(filepath); + let processCount = 1, + next = true, + nextToken, + total = maxRecords; - if (!filename.endsWith('.xml')) { - logger.warn(`ignoring non-xml file: ${filename}`); - continue; + while (next) { + if (nextToken) { + optionsWithToken = { pageToken: nextToken, ...options }; + } else { + optionsWithToken = options; } + const trials = await requestWithRetry({ + json: true, + method: 'GET', + qs: { + aggFilters: 'studyType:int', + countTotal: true, + pageSize: 1000, + 'query.cond': 'cancer', + sort: 'LastUpdatePostDate', + ...optionsWithToken, + }, + uri: BASE_URL, + }); - try { - const xml = await loadXmlToJson(filepath); - const record = convertAPIRecord(xml); - await processRecord({ - conn, record, source, upsert: true, - }); - counts.success++; - } catch (err) { - logger.error(`[${filename}] ${err}`); - counts.error++; + if (!total) { + total = trials.totalCount; } - } - logger.info(JSON.stringify(counts)); -}; - -/** - * Parses clinical trial RSS Feed results for clinical trials in Canada and the US - * which were updated in the last 2 weeks - */ -const loadNewTrials = async ({ conn }) => { - // ping them both to get the list of recently updated trials - const recentlyUpdatedTrials = []; - - const resp = await requestWithRetry({ - method: 'GET', - qs: { - cond: 'cancer', // cancer related trials - count: 10000, - lup_d: 14, - rcv_d: '', - recrs: 'abdef', - sel_rss: 'mod14', // mod14 for last 2 weeks updated - type: 'Intr', // interventional only - }, - uri: RSS_URL, - }); - const xml = await parseXmlToJson(resp); - fs.writeFileSync('output.json', JSON.stringify(xml, null, 2)); - checkSpec(validateRssFeed, xml); - recentlyUpdatedTrials.push( - ...xml.rss.channel[0].item.map(item => item.guid[0]._), - ); + if (processCount > total) { + break; + } - logger.info(`loading ${recentlyUpdatedTrials.length} recently updated trials`); - const counts = { error: 0, success: 0 }; + for (const trial of trials.studies) { + if (processCount > total) { + break; + } - for (const trialId of recentlyUpdatedTrials) { - try { - await fetchAndLoadById(conn, trialId, { upsert: true }); - counts.success++; - } catch (err) { - counts.error++; - logger.error(`[${trialId}] ${err}`); + try { + const record = convertAPIRecord(trial); + logger.info(`processing (${processCount}/${total}) record: ${record.sourceId}`); + processCount++; + await processRecord({ + conn, record, source, upsert: true, + }); + counts.success++; + } catch (err) { + counts.error++; + logger.error(`[${trial}] ${err}`); + } } + + nextToken = trials.nextPageToken; + next = nextToken !== undefined; } logger.info(JSON.stringify(counts)); }; @@ -399,6 +377,5 @@ module.exports = { convertAPIRecord, fetchAndLoadById, kb: true, - upload: loadNewTrials, - uploadFiles, + upload, }; diff --git a/src/clinicaltrialsgov/specs.json b/src/clinicaltrialsgov/specs.json index 1b3e85cd..ecb7386a 100644 --- a/src/clinicaltrialsgov/specs.json +++ b/src/clinicaltrialsgov/specs.json @@ -1,303 +1,151 @@ { - "api": { - "properties": { - "clinical_study": { - "properties": { - "brief_title": { - "items": { - "type": "string" - }, - "maxItems": 1, - "minItems": 1, - "type": "array" - }, - "completion_date": { - "items": { - "oneOf": [ - { - "properties": { - "_": { - "type": "string" - } - }, - "required": [ - "_" - ], - "type": "object" + "studies": { + "properties":{ + "protocolSection":{ + "properties":{ + "identificationModule":{ + "properties":{ + "nctId":{ + "items": { + "pattern": "^NCT\\d+$" }, - { - "type": "string" - } - ] - }, - "maxItems": 1, - "minItems": 1, - "type": "array" - }, - "condition": { - "items": { - "type": "string" - }, - "type": "array" - }, - "id_info": { - "items": { - "properties": { - "nct_id": { - "items": { - "pattern": "^NCT\\d+$" - }, - "maxItems": 1, - "minItems": 1, - "type": "array" - } + "type": "string" }, - "required": [ - "nct_id" - ], - "type": "object" + "briefTitle":{ + "type": "string" + } }, - "maxItems": 1, - "minItems": 1, - "type": "array" + "required":[ + "nctId", + "briefTitle" + ], + "type": "object" }, - "intervention": { - "items": { - "properties": { - "intervention_name": { - "items": { + "statusModule":{ + "properties":{ + "lastUpdatePostDateStruct":{ + "properties":{ + "date": { "type": "string" - }, - "maxItems": 1, - "minItems": 1, - "type": "array" + } }, - "intervention_type": { - "items": { - "type": "string" - }, - "maxItems": 1, - "minItems": 1, - "type": "array" - } - }, - "required": [ - "intervention_type", - "intervention_name" - ], - "type": "object" - }, - "type": "array" - }, - "last_update_posted": { - "items": { - "properties": { - "_": { - "type": "string" - } + "required":[ + "date" + ], + "type": "object" }, - "required": [ - "_" - ], - "type": "object" + "overallStatus":{ + "type": "string" + } }, - "maxItems": 1, - "minItems": 1, - "type": "array" - }, - "location": { - "items": { - "properties": { - "facility": { - "items": { - "properties": { - "address": { - "items": { - "properties": { - "city": { - "items": { - "type": "string" - }, - "maxItems": 1, - "minItems": 1, - "type": "array" - }, - "country": { - "items": { - "type": "string" - }, - "maxItems": 1, - "minItems": 1, - "type": "array" - } - }, - "required": [ - "city", - "country" - ], - "type": "object" - }, - "maxItems": 1, - "minItems": 1, - "type": "array" - } - }, - "type": "object" - }, - "maxItems": 1, - "minItems": 1, - "type": "array" - } + "optionalProperties":{ + "startDateStruct":{ + "properties":{ + "date": { + "type": "string" + } + }, + "required":[ + "date" + ], + "type": "object" }, - "required": [ - "facility" - ], - "type": "object" - }, - "minItems": 1, - "type": "array" - }, - "official_title": { - "items": { - "type": "string" - }, - "maxItems": 1, - "minItems": 1, - "type": "array" - }, - "overall_status": { - "items": { - "type": "string" + "completionDateStruct":{ + "properties":{ + "date": { + "type": "string" + } + }, + "required":[ + "date" + ], + "type": "object" + } }, - "maxItems": 1, - "minItems": 1, - "type": "array" + "required":[ + "lastUpdatePostDateStruct", + "overallStatus" + ], + "type": "object" }, - "phase": { - "items": { - "type": "string" + "conditionsModule":{ + "properties":{ + "conditions":{ + "items": { + "type": "string" + }, + "type": "array" + } }, - "maxItems": 1, - "minItems": 1, - "type": "array" + "required":[ + "conditions" + ], + "type": "object" }, - "required_header": { - "items": { - "properties": { - "url": { - "items": { - "type": "string" - }, - "maxItems": 1, - "minItems": 1, - "type": "array" - } - }, - "required": [ - "url" - ], - "type": "object" + "designModule":{ + "properties":{ + "phases":{ + "items": { + "type": "string" + }, + "type": "array" + } }, - "maxItems": 1, - "minItems": 1, - "type": "array" + "required":[ + "phases" + ], + "type": "object" }, - "start_date": { - "items": { - "oneOf": [ - { - "properties": { - "_": { - "type": "string" - } - }, - "required": [ - "_" - ], - "type": "object" + "armsInterventionsModule":{ + "properties":{ + "interventions":{ + "properties":{ + "type":{"type": "string"}, + "name":{"type": "string"} }, - { - "type": "string" - } - ] + "required":[ + "type", + "name" + ], + "type": "array" + } }, - "maxItems": 1, - "minItems": 1, - "type": "array" + "required":[ + "interventions" + ], + "type": "object" } }, - "required": [ - "id_info", - "brief_title", - "phase", - "condition", - "intervention", - "last_update_posted", - "required_header", - "overall_status" - ], - "type": "object" - } - }, - "required": [ - "clinical_study" - ], - "type": "object" - }, - "rss": { - "properties": { - "rss": { - "properties": { - "channel": { - "items": { - "properties": { - "item": { - "items": { - "properties": { - "guid": { - "items": { - "properties": { - "_": { - "pattern": "^NCT\\d+$", - "type": "string" - } - }, - "required": [ - "_" - ], - "type": "object" - }, - "maxItems": 1, - "minItems": 1, - "type": "array" - } - }, - "required": [ - "guid" - ], - "type": "object" - }, - "type": "array" - } - }, - "required": [ - "item" - ], - "type": "object" + "optionalProperties":{ + "contactsLocationsModule":{ + "optionalProperties":{ + "locations":{ + "properties":{ + "city":{"type": "string"}, + "country":{"type": "string"} + }, + "required":[ + "city", + "country" + ], + "type":"array" + } }, - "maxItems": 1, - "minItems": 1, - "type": "array" + "type": "object" } }, - "required": [ - "channel" + "required":[ + "identificationModule", + "statusModule", + "conditionsModule", + "designModule", + "armsInterventionsModule" ], "type": "object" } }, - "required": [ - "rss" + "required":[ + "protocolSection" ], "type": "object" } diff --git a/src/dgidb/index.js b/src/dgidb/index.js index 4ba114b6..48690620 100644 --- a/src/dgidb/index.js +++ b/src/dgidb/index.js @@ -49,7 +49,7 @@ const processRecord = async ({ conn, record, source }) => { const upload = async ({ conn, url = BASE_URL }) => { logger.info('creating the source record'); const source = rid(await conn.addSource(SOURCE_DEFN)); - const limit = 1000; + const limit = 100; let page = `${url}/interactions?count=${limit}&page=1`; const counts = { error: 0, skip: 0, success: 0 }; diff --git a/src/diseaseOntology/index.js b/src/diseaseOntology/index.js index 772bdddf..dab13d8e 100644 --- a/src/diseaseOntology/index.js +++ b/src/diseaseOntology/index.js @@ -252,6 +252,7 @@ const uploadFile = async ({ alias: false, deprecated, description, + displayName: `${name} [${sourceId.toUpperCase()}]`, name, source, sourceId, @@ -286,6 +287,7 @@ const uploadFile = async ({ for (const alias of aliases) { const content = { alias: true, + displayName: `${alias} [${record.sourceId.toUpperCase()}]`, name: alias, source, sourceId: record.sourceId, @@ -331,6 +333,7 @@ const uploadFile = async ({ for (const alternateId of hasDeprecated) { const content = { deprecated: true, + displayName: `${record.name} [${alternateId.toUpperCase()}]`, name: record.name, source, sourceId: alternateId, diff --git a/src/docm/index.js b/src/docm/index.js index 6c047646..4b7d5f60 100644 --- a/src/docm/index.js +++ b/src/docm/index.js @@ -178,6 +178,13 @@ const processRecord = async (opt) => { if (!variant) { throw new Error('Failed to parse either variant'); } + // get the vocabulary term + // KBDEV-1050: treat all incoming docm relevance as recurrent + const relevance = await conn.getVocabularyTerm('recurrent'); + + if (!relevance) { + throw new Error('Unable to find recurrent as relevance'); + } for (const diseaseRec of record.diseases) { if (!diseaseRec.tags || diseaseRec.tags.length !== 1) { @@ -186,8 +193,6 @@ const processRecord = async (opt) => { } try { - // get the vocabulary term - const relevance = await conn.getVocabularyTerm(diseaseRec.tags[0]); // get the disease by name const disease = await conn.getUniqueRecordBy({ filters: { diff --git a/src/drugbank/README.md b/src/drugbank/README.md index 8a9d3de2..97cba1e3 100644 --- a/src/drugbank/README.md +++ b/src/drugbank/README.md @@ -18,7 +18,7 @@ mv full\ database.xml $filename Then use the general file loader to load this into GraphKB ```bash -node bin/load.js file drugbank_all_full_database_*.xml +node bin/load.js file drugbank full_database.xml ``` > :warning: Since this contains cross-mappings to [FDA-SRS](../fdaSrs) UNII identifiers it is useful to load that file diff --git a/src/ensembl/README.md b/src/ensembl/README.md index 2c45816b..d2302984 100644 --- a/src/ensembl/README.md +++ b/src/ensembl/README.md @@ -4,10 +4,12 @@ This loader loads both a BioMart export TSV file or individual records by ID. It to batch load Ensembl data but you can do so if you would like it to appear for users who will use the auto-complete adding variants through GraphKB client +Link for archived ensembl versions: https://useast.ensembl.org/info/website/archives/index.html + First download the batch export from BioMart ```bash -query_string='' +query_string='' wget -O biomart_export.tsv "http://www.ensembl.org/biomart/martservice?query=$query_string" ``` diff --git a/src/ensembl/index.js b/src/ensembl/index.js index 749f50d0..5009dc32 100644 --- a/src/ensembl/index.js +++ b/src/ensembl/index.js @@ -6,13 +6,12 @@ const { loadDelimToJson, requestWithRetry, convertRowFields } = require('../util'); const { - rid, orderPreferredOntologyTerms, generateCacheKey, + rid, generateCacheKey, } = require('../graphkb'); const { logger } = require('../logging'); const _hgnc = require('../hgnc'); const _entrez = require('../entrez/gene'); -const _refseq = require('../entrez/refseq'); -const { ensembl: SOURCE_DEFN, refseq: refseqSourceDefn } = require('../sources'); +const { ensembl: SOURCE_DEFN } = require('../sources'); const BASE_URL = 'http://rest.ensembl.org'; @@ -181,7 +180,7 @@ const uploadFile = async (opt) => { const HEADER = { geneIdVersion: 'Gene stable ID version', hgncId: 'HGNC ID', - refseqId: 'RefSeq mRNA ID', + proteinIdVersion: 'Protein stable ID version', transcriptIdVersion: 'Transcript stable ID version', }; const { filename, conn } = opt; @@ -190,24 +189,22 @@ const uploadFile = async (opt) => { // process versions for (const row of rows) { - [row.geneId, row.geneIdVersion] = row.geneIdVersion.split('.'); - [row.transcriptId, row.transcriptIdVersion] = row.transcriptIdVersion.split('.'); + [row.geneId, row.geneIdVersion] = row.geneIdVersion.toLowerCase().split('.'); + [row.transcriptId, row.transcriptIdVersion] = row.transcriptIdVersion.toLowerCase().split('.'); + [row.proteinId, row.proteinIdVersion] = row.proteinIdVersion.toLowerCase().split('.'); } const source = await conn.addSource(SOURCE_DEFN); - const refseqSource = await conn.addSource(refseqSourceDefn); - const visited = {}; // cache genes to speed up adding records const hgncMissingRecords = new Set(); - const refseqMissingRecords = new Set(); logger.info('pre-load the entrez cache to avoid unecessary requests'); await _entrez.preLoadCache(conn); // skip any genes that have already been loaded before we start logger.info('retreiving the list of previously loaded genes'); - const preLoaded = new Set(); + const preLoadedGene = new Set(); const genesList = await conn.getRecords({ filters: { AND: [ @@ -218,25 +215,48 @@ const uploadFile = async (opt) => { target: 'Feature', }); + const preLoadedTranscript = new Set(); + const transcriptList = await conn.getRecords({ + filters: { + AND: [ + { source: rid(source) }, { biotype: 'transcript' }, { dependency: null }, + ], + }, + neighbors: 0, + target: 'Feature', + }); + + const preLoadedProtein = new Set(); + const proteinList = await conn.getRecords({ + filters: { + AND: [ + { source: rid(source) }, { biotype: 'protein' }, { dependency: null }, + ], + }, + neighbors: 0, + target: 'Feature', + }); + const counts = { error: 0, skip: 0, success: 0 }; for (const record of genesList) { const gene = generateCacheKey(record); - preLoaded.add(gene); - logger.info(`${gene} has already been loaded`); + preLoadedGene.add(gene); + logger.info(`Gene ${gene} has already been loaded`); } - logger.info('pre-fetching refseq entries'); - await _refseq.preLoadCache(conn); - const missingRefSeqIds = new Set(); - rows.map(r => r.refseqId || '').forEach((id) => { - if (!_refseq.cacheHas(id) && id) { - missingRefSeqIds.add(id); - } - }); + for (const record of transcriptList) { + const transcript = generateCacheKey(record); + preLoadedTranscript.add(transcript); + logger.info(`Transcript ${transcript} has already been loaded`); + } + + for (const record of proteinList) { + const protein = generateCacheKey(record); + preLoadedProtein.add(protein); + logger.info(`Protein ${protein} has already been loaded`); + } - logger.info(`fetching ${missingRefSeqIds.size} missing refseq entries`); - await _refseq.fetchAndLoadByIds(conn, Array.from(missingRefSeqIds)); logger.info(`processing ${rows.length} records`); @@ -244,134 +264,210 @@ const uploadFile = async (opt) => { const record = rows[index]; const { geneId, geneIdVersion } = record; + const { transcriptId, transcriptIdVersion } = record; + const { proteinId, proteinIdVersion } = record; - const key = generateCacheKey({ sourceId: geneId, sourceIdVersion: geneIdVersion }); + const geneVersion = generateCacheKey({ + sourceId: geneId, + sourceIdVersion: geneIdVersion, + }); + const transcriptVersion = generateCacheKey({ + sourceId: transcriptId, + sourceIdVersion: transcriptIdVersion, + }); + const proteinVersion = generateCacheKey({ + sourceId: proteinId, + sourceIdVersion: proteinIdVersion, + }); - if (preLoaded.has(key)) { - counts.skip++; - continue; - } logger.info(`processing ${geneId}.${geneIdVersion || ''} (${index} / ${rows.length})`); - let newGene = false; - if (visited[key] === undefined) { - visited[key] = await conn.addRecord({ + let newGene = false, + skip = 0; + + if (preLoadedGene.has(geneVersion)) { + visited[geneVersion] = genesList.find((gene) => `${gene.sourceId}-${gene.sourceIdVersion}` === geneVersion); + visited[geneId] = genesList.find((gene) => `${gene.sourceId}` === geneId && gene.sourceIdVersion === null); + skip++; + } else { + if (visited[geneVersion] === undefined) { + visited[geneVersion] = await conn.addRecord({ + content: { + biotype: 'gene', + source: rid(source), + sourceId: geneId, + sourceIdVersion: geneIdVersion, + }, + existsOk: true, + target: 'Feature', + }); + } + + if (visited[geneId] === undefined) { + newGene = true; + visited[geneId] = await conn.addRecord({ + content: { + biotype: 'gene', + source: rid(source), + sourceId: geneId, + sourceIdVersion: null, + }, + existsOk: true, + target: 'Feature', + }); + } + + await conn.addRecord({ content: { - biotype: 'gene', - source: rid(source), - sourceId: geneId, - sourceIdVersion: geneIdVersion, + in: rid(visited[geneVersion]), out: rid(visited[geneId]), source: rid(source), }, existsOk: true, - target: 'Feature', + fetchExisting: false, + target: 'generalizationof', }); } - if (visited[geneId] === undefined) { - newGene = true; - visited[geneId] = await conn.addRecord({ + const gene = visited[geneId]; + const versionedGene = visited[geneVersion]; + + // transcript + if (preLoadedTranscript.has(transcriptVersion)) { + visited[transcriptVersion] = transcriptList.find((transcript) => `${transcript.sourceId}-${transcript.sourceIdVersion}` === transcriptVersion); + visited[transcriptId] = transcriptList.find((transcript) => `${transcript.sourceId}` === transcriptId && transcript.sourceIdVersion === null); + skip++; + } else { + if (visited[transcriptVersion] === undefined) { + visited[transcriptVersion] = await conn.addRecord({ + content: { + biotype: 'transcript', + source: rid(source), + sourceId: record.transcriptId, + sourceIdVersion: record.transcriptIdVersion, + }, + existsOk: true, + target: 'Feature', + }); + } + if (visited[transcriptId] === undefined) { + visited[transcriptId] = await conn.addRecord({ + content: { + biotype: 'transcript', + source: rid(source), + sourceId: record.transcriptId, + sourceIdVersion: null, + }, + existsOk: true, + target: 'Feature', + }); + // transcript -> elementof -> gene + await conn.addRecord({ + content: { + in: rid(gene), out: rid(visited[transcriptId]), source: rid(source), + }, + existsOk: true, + fetchExisting: false, + target: 'elementof', + }); + } + + await conn.addRecord({ content: { - biotype: 'gene', + in: rid(visited[transcriptVersion]), + out: rid(visited[transcriptId]), source: rid(source), - sourceId: geneId, - sourceIdVersion: null, }, existsOk: true, - target: 'Feature', + fetchExisting: false, + target: 'generalizationof', }); } - const gene = visited[geneId]; - const versionedGene = visited[key]; - await conn.addRecord({ - content: { - in: rid(versionedGene), out: rid(gene), source: rid(source), - }, - existsOk: true, - fetchExisting: false, - target: 'generalizationof', - }); + const versionedTranscript = visited[transcriptVersion]; + const transcript = visited[transcriptId]; - // transcript - const versionedTranscript = await conn.addRecord({ - content: { - biotype: 'transcript', - source: rid(source), - sourceId: record.transcriptId, - sourceIdVersion: record.transcriptIdVersion, - }, - existsOk: true, - target: 'Feature', - }); - const transcript = await conn.addRecord({ - content: { - biotype: 'transcript', - source: rid(source), - sourceId: record.transcriptId, - sourceIdVersion: null, - }, - existsOk: true, - target: 'Feature', - }); + // versioned: transcript -> elementof -> gene await conn.addRecord({ content: { - in: rid(versionedTranscript), out: rid(transcript), source: rid(source), - }, - existsOk: true, - fetchExisting: false, - target: 'generalizationof', - }); - - // transcript -> elementof -> gene - await conn.addRecord({ - content: { - in: rid(gene), out: rid(transcript), source: rid(source), - }, - existsOk: true, - fetchExisting: false, - target: 'elementof', - }); - await conn.addRecord({ - content: { - in: rid(versionedGene), out: rid(versionedTranscript), source: rid(source), + in: rid(versionedGene), + out: rid(versionedTranscript), + source: rid(source), }, existsOk: true, fetchExisting: false, target: 'elementof', }); - // TODO: protein - // TODO: protein -> elementof -> transcript - // transcript -> aliasof -> refseq - if (record.refseqId) { - try { - const refseq = await conn.getUniqueRecordBy({ - filters: { - AND: [ - { source: rid(refseqSource) }, - { sourceId: record.refseqId }, - { sourceIdVersion: null }, - ], + // protein + if (preLoadedProtein.has(proteinVersion)) { + visited[proteinVersion] = proteinList.find((protein) => `${protein.sourceId}-${protein.sourceIdVersion}` === proteinVersion); + visited[proteinId] = proteinList.find((protein) => `${protein.sourceId}` === proteinId && protein.sourceIdVersion === null); + skip++; + } else { + if (visited[proteinVersion] === undefined) { + visited[proteinVersion] = await conn.addRecord({ + content: { + biotype: 'protein', + source: rid(source), + sourceId: record.proteinId, + sourceIdVersion: record.proteinIdVersion, }, - sort: orderPreferredOntologyTerms, + existsOk: true, target: 'Feature', }); + } + if (visited[proteinId] === undefined) { + visited[proteinId] = await conn.addRecord({ + content: { + biotype: 'protein', + source: rid(source), + sourceId: record.proteinId, + sourceIdVersion: null, + }, + existsOk: true, + target: 'Feature', + }); + // protein -> elementof -> transcript await conn.addRecord({ content: { - in: rid(refseq), out: rid(transcript), source: rid(source), + in: rid(transcript), out: rid(visited[proteinId]), source: rid(source), }, existsOk: true, fetchExisting: false, - target: 'crossreferenceof', + target: 'elementof', }); - } catch (err) { - logger.log('error', `failed cross-linking from ${record.transcriptId} to ${record.refseqId}`); - refseqMissingRecords.add(record.refseqId); } + + await conn.addRecord({ + content: { + in: rid(visited[proteinVersion]), + out: rid(visited[proteinId]), + source: rid(source), + }, + existsOk: true, + fetchExisting: false, + target: 'generalizationof', + }); } - // gene -> aliasof -> hgnc + + // versioned: protein -> elementof -> transcript + await conn.addRecord({ + content: { + in: rid(versionedTranscript), + out: rid(visited[proteinVersion]), + source: rid(source), + }, + existsOk: true, + fetchExisting: false, + target: 'elementof', + }); + + + + // gene -> crossreferenceof -> hgnc if (record.hgncId && newGene) { + skip--; + try { const hgnc = await _hgnc.fetchAndLoadBySymbol({ conn, paramType: 'hgnc_id', symbol: record.hgncId }); await conn.addRecord({ @@ -387,15 +483,17 @@ const uploadFile = async (opt) => { logger.log('error', `failed cross-linking from ${gene.sourceid} to ${record.hgncId}`); } } + if (skip === 3) { + counts.skip++; + continue; + } counts.success++; } if (hgncMissingRecords.size) { logger.warn(`Unable to retrieve ${hgncMissingRecords.size} hgnc records for linking`); } - if (refseqMissingRecords.size) { - logger.warn(`Unable to retrieve ${refseqMissingRecords.size} refseq records for linking`); - } + logger.info(JSON.stringify(counts)); }; diff --git a/src/entrez/gene.js b/src/entrez/gene.js index 2c7b428a..c7c645d6 100644 --- a/src/entrez/gene.js +++ b/src/entrez/gene.js @@ -49,19 +49,29 @@ const parseRecord = (record) => { * * @param {ApiConnection} api connection to GraphKB * @param {Array.} idList list of gene IDs + * @param {object} opt + * @param {boolean} opt.fetchFirst override util.uploadRecord() fetchFirst + * @param {boolean} opt.upsert override util.uploadRecord() upsert */ -const fetchAndLoadGeneByIds = async (api, idListIn) => util.fetchAndLoadByIds( - api, - idListIn, - { - MAX_CONSEC, - cache: CACHE, - dbName: DB_NAME, - parser: parseRecord, - sourceDefn: SOURCE_DEFN, - target: 'Feature', - }, -); +const fetchAndLoadGeneByIds = async (api, idListIn, opt = {}) => { + // For record update, set fetchFirst to false & upsert to true. + const { fetchFirst, upsert } = opt; + + return util.fetchAndLoadByIds( + api, + idListIn, + { + MAX_CONSEC, + cache: CACHE, + dbName: DB_NAME, + fetchFirst, + parser: parseRecord, + sourceDefn: SOURCE_DEFN, + target: 'Feature', + upsert, + }, + ); +}; /** * Given a gene symbol, search the genes and upload the resulting records to graphkb diff --git a/src/entrez/pubmed.js b/src/entrez/pubmed.js index 10bb66cf..f1adbbb6 100644 --- a/src/entrez/pubmed.js +++ b/src/entrez/pubmed.js @@ -7,7 +7,6 @@ const { checkSpec } = require('../util'); const { fetchByIdList, uploadRecord, preLoadCache: preLoadAnyCache } = require('./util'); const ajv = new Ajv(); -const LINK_URL = 'https://pubmed.ncbi.nlm.nih.gov'; const { pubmed: SOURCE_DEFN } = require('../sources'); @@ -35,7 +34,7 @@ const parseRecord = (record) => { const parsed = { name: record.title, sourceId: record.uid, - url: `${LINK_URL}/${record.uid}`, + url: `${SOURCE_DEFN.url}/${record.uid}`, }; if (record.fulljournalname) { diff --git a/src/entrez/util.js b/src/entrez/util.js index afb6ee7e..8dc7bcd4 100644 --- a/src/entrez/util.js +++ b/src/entrez/util.js @@ -138,6 +138,7 @@ const fetchRecord = async (api, { * @param {boolean} opt.fetchFirst attempt to get the record by source Id before uploading it * @param {string} opt.target * @param {object} opt.sourceDefn + * @param {boolean} opt.upsert update the record if already exists * @param {function} opt.createDisplayName */ const uploadRecord = async (api, content, opt = {}) => { @@ -257,14 +258,23 @@ const preLoadCache = async (api, { sourceDefn, cache, target }) => { * @param {Array.} idListIn list of pubmed IDs * @param {Object} opt * @param {string} opt.dbName name of the entrez db to pull from ex. gene + * @param {boolean} opt.fetchFirst override uploadRecord() fetchFirst * @param {function} opt.parser function to convert records from the api to the graphkb format * @param {object} opt.cache * @param {number} opt.MAX_CONSEC maximum consecutive records to upload at once * @param {string} opt.target the graphkb api target to upload to * @param {object} opt.sourceDefn the object with the source information + * @param {boolean} opt.upsert override uploadRecord() upsert */ const fetchAndLoadByIds = async (api, idListIn, { - dbName, parser, cache, MAX_CONSEC = 100, target, sourceDefn, + dbName, + fetchFirst, + parser, + cache, + MAX_CONSEC = 100, + target, + sourceDefn, + upsert, }) => { const records = await fetchByIdList( idListIn, @@ -281,8 +291,10 @@ const fetchAndLoadByIds = async (api, idListIn, { const newRecords = await Promise.all(current.map( async record => uploadRecord(api, record, { cache, + fetchFirst, sourceDefn, target, + upsert, }), )); result.push(...newRecords); diff --git a/src/graphkb.js b/src/graphkb.js index acc4333a..914d2e01 100644 --- a/src/graphkb.js +++ b/src/graphkb.js @@ -557,6 +557,7 @@ class ApiConnection { const model = schema.get(target); const filters = fetchConditions || convertRecordToQueryFilters(content); + // Will first try to fetch and/or update the record if it already exists if (fetchFirst || upsert) { try { const result = await this.getUniqueRecordBy({ @@ -577,6 +578,7 @@ class ApiConnection { throw new Error(`cannot find model from target (${target})`); } + // Then (since record dosen't already exists) will create a new record try { const { result } = jc.retrocycle(await this.request({ body: content, diff --git a/src/moa/index.js b/src/moa/index.js index 2a410c79..63598a70 100644 --- a/src/moa/index.js +++ b/src/moa/index.js @@ -12,7 +12,7 @@ const pubmed = require('../entrez/pubmed'); const _trials = require('../clinicaltrialsgov'); -const ajv = new Ajv({ allErrors: true }); +const ajv = new Ajv({ allErrors: true, coerceTypes: true }); const validateMoaRecord = ajv.compile(spec); @@ -459,6 +459,9 @@ const fixStringNulls = (obj) => { /** * Any values of "None" replace with null */ + if (obj === '') { + return null; + } if (obj === 'None') { return null; } @@ -551,7 +554,6 @@ const upload = async ({ conn, url = 'https://moalmanac.org/api/assertions' }) => logger.info(`loading: ${rawRecord.assertion_id} / ${records.length}`); const record = fixStringNulls(rawRecord); checkSpec(validateMoaRecord, record); - const key = `${record.assertion_id}`; const lastUpdate = new Date(record.last_updated).getTime(); const relevance = parseRelevance(record); diff --git a/src/ncit/README.md b/src/ncit/README.md index bc747a81..e137b2d5 100644 --- a/src/ncit/README.md +++ b/src/ncit/README.md @@ -3,8 +3,9 @@ First download the latest version of the plain text tab delimited files. This should include both the main thesaurus file and the cross mapping file -- [Load the Main Flat File](#load-the-main-flat-file) -- [FDA Cross Mapping File](#fda-cross-mapping-file) +- [NCI Thesaurus](#nci-thesaurus) + - [Load the Main Flat File](#load-the-main-flat-file) + - [FDA Cross Mapping File](#fda-cross-mapping-file) ## Load the Main Flat File @@ -20,18 +21,19 @@ This is a headerless tab delimited file with the following [format](https://evs.nci.nih.gov/ftp1/NCI_Thesaurus/ReadMe.txt) - code -- concept name +- concept IRI - parents - synonyms - definition - display name - concept status - semantic type +- concept in subset Next use the general file loader to load the NCIt terms ```bash -node bin/loadFile ncit Thesaurus.txt +node bin/load.js file ncit Thesaurus.txt ``` ## FDA Cross Mapping File @@ -46,5 +48,5 @@ Then, after you have loaded the [FDA-SRS](../fdaSrs) data (if you are planning t load the cross-reference mapping data ```bash -node bin/loadFile ncitFdaXref FDA-UNII_NCIt_Subsets.txt +node bin/load.js file ncitFdaXref FDA-UNII_NCIt_Subsets.txt ``` diff --git a/src/ncit/index.js b/src/ncit/index.js index 562ee06f..a85a2411 100644 --- a/src/ncit/index.js +++ b/src/ncit/index.js @@ -181,6 +181,7 @@ const cleanRawRow = (rawRow) => { endpoint, name: row.name.toLowerCase(), sourceId, + original_synonyms: row.synonyms, synonyms: Array.from(new Set(row.synonyms)) .map(s => s.toLowerCase()) .filter(s => s !== row.name.toLowerCase()), @@ -211,6 +212,7 @@ const uploadFile = async ({ 'name', 'conceptStatus', 'semanticType', + 'conceptInSubset', ], }); // determine unresolvable records @@ -285,11 +287,24 @@ const uploadFile = async ({ } } + // check list of synonyms to find non-duplicate names - first position in this list is the 'preferred name' from ncit + const preferredNames = dups.map(dup => dup.original_synonyms[0]); + const allPreferredNamesDifferent = () => new Set(preferredNames).size === preferredNames.length; + + if (allPreferredNamesDifferent) { + for (const dup of dups) { + dup.name = dup.original_synonyms[0]; + logger.log('info', `record with non-unique name (${name}, ${dup.sourceId}) being loaded with its preferred name (${dup.name});`); + } + continue; + } + if (name && humanDups.length > 1) { logger.warn(`ncit terms (${humanDups.map(r => r.sourceId).join(', ')}) have non-unique name (${name})`); humanDups.forEach(d => rejected.add(d.sourceId)); } } + logger.warn(`rejected ${rejected.size} rows for unresolveable primary/display name conflicts`); const source = rid(await conn.addSource(SOURCE_DEFN)); diff --git a/src/oncotree/index.js b/src/oncotree/index.js index c03c0072..811b37b3 100644 --- a/src/oncotree/index.js +++ b/src/oncotree/index.js @@ -211,6 +211,7 @@ const upload = async (opt) => { // upload the results for (const record of records) { const body = { + displayName: `${record.name} [${record.sourceId.toUpperCase()}]`, name: record.name, source: rid(source), sourceId: record.sourceId, diff --git a/src/ontology/README.md b/src/ontology/README.md index 4a1b93d9..c2fee005 100644 --- a/src/ontology/README.md +++ b/src/ontology/README.md @@ -80,5 +80,5 @@ the ontology term Once this file has been built it can be loaded as follows. The script will create records if they do not already exist. Any conflicts will be reported in the logging ```bash -node bin/ontology.js --filename /path/to/json/file +node bin/load.js file ontology ``` diff --git a/src/refseq/index.js b/src/refseq/index.js index 89ce26ef..1b298f91 100644 --- a/src/refseq/index.js +++ b/src/refseq/index.js @@ -28,7 +28,13 @@ const uploadFile = async ({ filename, conn, maxRecords }) => { fetchConditions: { name: SOURCE_DEFN.name }, target: 'Source', }); - logger.log('info', `Loading ${json.length} gene records`); + + if (maxRecords) { + logger.log('info', `Loading ${maxRecords} gene records`); + } else { + logger.log('info', `Loading ${json.length} gene records`); + } + const counts = { error: 0, skipped: 0, success: 0 }; // batch load entrez genes await _entrez.preLoadCache(conn); diff --git a/src/sources.js b/src/sources.js index 74ae5863..d6ffae0e 100644 --- a/src/sources.js +++ b/src/sources.js @@ -145,6 +145,15 @@ const sources = { url: 'https://www.ncbi.nlm.nih.gov/gene', usage: 'https://www.ncbi.nlm.nih.gov/home/about/policies', }, + esmo: { + description: 'Esmo is the leading professional organisation for medical oncology. with more than 25,000 members representing oncology professionals from over 160 countries worldwide, esmo is the society of reference for oncology education and information.', + displayName: 'ESMO', + longName: 'European Society for Medical Oncology', + name: 'esmo', + sort: 1, + url: 'https://www.esmo.org', + usage: 'https://www.esmo.org/terms-of-use/website-terms-conditions', + }, fdaApprovals: { displayName: 'FDA Approvals', longName: 'FDA Hematology/Oncology (Cancer) Approvals & Safety Notifications', @@ -229,11 +238,18 @@ const sources = { sort: 0, url: 'http://oncotree.mskcc.org', }, + profyle: { + description: 'PROFYLE (PRecision Oncology For Young peopLE) is a pan-Canadian research project for children and young adults ages 29 and under who have been told they are out of treatment options, giving them another chance.', + displayName: 'PROFYLE', + name: 'profyle', + sort: 99999, + url: 'https://www.profyle.ca/', + }, pubmed: { - description: 'pubmed comprises more than 29 million citations for biomedical literature from medline, life science journals, and online books. citations may include links to full-text content from pubmed central and publisher web sites', + description: 'PubMed is a free resource supporting the search and retrieval of biomedical and life sciences literature with the aim of improving health–both globally and personally.', displayName: 'PubMed', name: 'pubmed', - url: 'https://www.ncbi.nlm.nih.gov/pubmed', + url: 'https://pubmed.ncbi.nlm.nih.gov', usage: 'https://www.ncbi.nlm.nih.gov/home/about/policies', }, refseq: { @@ -250,6 +266,15 @@ const sources = { url: 'http://www.sequenceontology.org', usage: 'http://www.sequenceontology.org/?page_id=269', }, + tso500: { + description: 'TruSight Oncology 500 is a next-generation sequencing (NGS) assay that enables in-house comprehensive genomic profiling of tumor samples.', + displayName: 'TSO500', + longName: 'TruSight Oncology 500', + name: 'tso500', + sort: 99999, + url: 'https://www.illumina.com', + usage: 'https://www.illumina.com/products/by-type/clinical-research-products/trusight-oncology-500.html', + }, uberon: { comment: 'https://github.com/obophenotype/uberon/issues/1139', description: 'Uberon is an integrated cross-species ontology covering anatomical structures in animals.', diff --git a/src/util.js b/src/util.js index 6fa40c8e..45c492ab 100644 --- a/src/util.js +++ b/src/util.js @@ -234,6 +234,7 @@ module.exports = { hashStringToId, loadDelimToJson, loadXmlToJson, + logger, parseXmlToJson, request, requestWithRetry, diff --git a/test/civic/civic.profile.test.js b/test/civic/civic.profile.test.js new file mode 100644 index 00000000..ee8ea60d --- /dev/null +++ b/test/civic/civic.profile.test.js @@ -0,0 +1,241 @@ +const { MolecularProfile } = require('../../src/civic/profile'); + + +describe('MolecularProfile._combine()', () => { + test.each([ + [{ arr1: [[]], arr2: [[]] }, [[]]], + [{ arr1: [['A']], arr2: [[]] }, [['A']]], + [{ arr1: [[]], arr2: [['B']] }, [['B']]], + [{ arr1: [['A']], arr2: [['B']] }, [['A', 'B']]], + [{ arr1: [['A']], arr2: [['B'], ['C']] }, [['A', 'B'], ['A', 'C']]], + [{ arr1: [['A'], ['B']], arr2: [['C'], ['D']] }, [['A', 'C'], ['A', 'D'], ['B', 'C'], ['B', 'D']]], + ])( + 'combine some conditions', ({ arr1, arr2 }, expected) => { + expect(MolecularProfile()._combine({ arr1, arr2 })).toEqual(expected); + }, + ); +}); + +describe('MolecularProfile._compile()', () => { + test.each([ + [[['A', 'B']], 'AND', [['C', 'D']], [['A', 'B', 'C', 'D']]], + [[['A', 'B']], 'AND', [['C', 'D'], ['E', 'F']], [['A', 'B', 'C', 'D'], ['A', 'B', 'E', 'F']]], + [[['A', 'B'], ['C', 'D']], 'AND', [['E', 'F']], [['A', 'B', 'E', 'F'], ['C', 'D', 'E', 'F']]], + [[['A', 'B']], 'OR', [['C', 'D']], [['A', 'B'], ['C', 'D']]], + [[['A', 'B']], 'OR', [['C', 'D'], ['E', 'F']], [['A', 'B'], ['C', 'D'], ['E', 'F']]], + [[['A', 'B'], ['C', 'D']], 'OR', [['E', 'F']], [['A', 'B'], ['C', 'D'], ['E', 'F']]], + ])( + 'compile somme expressions', (arr, op, part, expected) => { + expect(MolecularProfile()._compile({ arr, op, part })).toEqual(expected); + }, + ); +}); + +describe('MolecularProfile._disambiguate()', () => { + test('disambiguate conditions in AND statements', () => { + const Mp = MolecularProfile(); + Mp.conditions = [ + [{ id: 8, name: 'X123M/N' }, { id: 9, name: 'X456O/P' }, { id: 10, name: 'X456Q' }], + ]; + expect(Mp._disambiguate().conditions).toEqual( + [ + [{ id: 8, name: 'X123M' }, { id: 9, name: 'X456O' }, { id: 10, name: 'X456Q' }], + [{ id: 8, name: 'X123M' }, { id: 9, name: 'X456P' }, { id: 10, name: 'X456Q' }], + [{ id: 8, name: 'X123N' }, { id: 9, name: 'X456O' }, { id: 10, name: 'X456Q' }], + [{ id: 8, name: 'X123N' }, { id: 9, name: 'X456P' }, { id: 10, name: 'X456Q' }], + ], + ); + }); + + test('disambiguate conditions in OR statements', () => { + const Mp = MolecularProfile(); + Mp.conditions = [ + [{ id: 8, name: 'X123M/N' }], + [{ id: 9, name: 'X456O/P' }], + ]; + expect(Mp._disambiguate().conditions).toEqual( + [ + [{ id: 8, name: 'X123M' }], + [{ id: 8, name: 'X123N' }], + [{ id: 9, name: 'X456O' }], + [{ id: 9, name: 'X456P' }], + ], + ); + }); +}); + +describe('MolecularProfile._end()', () => { + const block = [ + { id: 1 }, { text: 'AND' }, + { text: '(' }, { id: 2 }, { text: 'OR' }, { id: 3 }, { text: ')' }, { text: 'AND' }, + { text: '(' }, { id: 4 }, { text: 'OR' }, + { text: '(' }, { id: 5 }, { text: 'AND' }, { id: 6 }, { text: ')' }, { text: ')' }, + ]; + + test.each([ + [2, 0, 4], + [4, 4, 8], + [6, 4, 6], + ])( + 'testing index and offset combinations: i=%s, offset=%s', (i, offset, expected) => { + expect(MolecularProfile()._end({ block, i, offset })).toEqual(expected); + }, + ); +}); + +describe('MolecularProfile._not()', () => { + test('check for presence of NOT operator in expression', () => { + expect(MolecularProfile()._not([ + { __typename: 'Feature' }, { id: 1 }, { text: 'AND' }, { text: 'NOT' }, { text: '(' }, + { __typename: 'Feature' }, { id: 2 }, { text: 'OR' }, { __typename: 'Feature' }, { id: 3 }, { text: ')' }, + ])).toBe(true); + expect(MolecularProfile()._not([ + { __typename: 'Feature' }, { id: 1 }, { text: 'AND' }, { text: '(' }, { __typename: 'Feature' }, + { id: 2 }, { text: 'OR' }, { __typename: 'Feature' }, { id: 3 }, { text: ')' }, + ])).toBe(false); + }); +}); + +describe('MolecularProfile._parse()', () => { + test.each([ + [ + [{ id: 1 }, { text: 'AND' }, { id: 2 }], + [[1, 2]], + ], + [ + [{ id: 1 }, { text: 'OR' }, { id: 2 }], + [[1], [2]], + ], + [ + [{ id: 1 }, { text: 'AND' }, { text: '(' }, { id: 2 }, { text: 'OR' }, { id: 3 }, { text: ')' }], + [[1, 2], [1, 3]], + ], + [ + [{ id: 1 }, { text: 'OR' }, { text: '(' }, { id: 2 }, { text: 'AND' }, { id: 3 }, { text: ')' }], + [[1], [2, 3]], + ], + [ + [ + { text: '(' }, { id: 1 }, { text: 'AND' }, { id: 2 }, { text: ')' }, + { text: 'OR' }, { text: '(' }, { id: 3 }, { text: 'AND' }, { id: 4 }, { text: ')' }, + ], + [[1, 2], [3, 4]], + ], + [ + [ + { text: '(' }, { id: 1 }, { text: 'OR' }, { id: 2 }, { text: ')' }, + { text: 'AND' }, { text: '(' }, { id: 3 }, { text: 'OR' }, { id: 4 }, { text: ')' }, + ], + [[1, 3], [1, 4], [2, 3], [2, 4]], + ], + [ + [ + { id: 1 }, { text: 'AND' }, { text: '(' }, { id: 2 }, { text: 'OR' }, { id: 3 }, { text: ')' }, + { text: 'AND' }, { text: '(' }, { id: 4 }, { text: 'OR' }, { id: 5 }, { text: ')' }, + ], + [[1, 2, 4], [1, 2, 5], [1, 3, 4], [1, 3, 5]], + ], + [ + [ + { id: 1 }, { text: 'OR' }, { text: '(' }, { id: 2 }, { text: 'AND' }, { id: 3 }, { text: ')' }, + { text: 'OR' }, { text: '(' }, { id: 4 }, { text: 'AND' }, { id: 5 }, { text: ')' }, + ], + [[1], [2, 3], [4, 5]], + ], + [ + [ + { id: 1 }, { text: 'AND' }, { text: '(' }, { id: 2 }, { text: 'AND' }, + { text: '(' }, { id: 3 }, { text: 'OR' }, { id: 4 }, { text: ')' }, { text: ')' }, + ], + [[1, 2, 3], [1, 2, 4]], + ], + ])( + 'testing some Molecular Profiles expressions', (block, expected) => { + expect(MolecularProfile()._parse(block)).toEqual(expected); + }, + ); +}); + +describe('MolecularProfile._split()', () => { + test.each([ + ['Q157P/R', [[{ name: 'Q157P' }], [{ name: 'Q157R' }]]], + ['Q157P', [[{ name: 'Q157P' }]]], + ])( + 'Split %s into its variations', (name, expected) => { + expect(MolecularProfile()._split({ name })).toEqual(expected); + }, + ); +}); + +describe('MolecularProfile._variants()', () => { + test('variants ids replaced by objects', () => { + const Mp = MolecularProfile({ + variants: [ + { id: 1, name: 'a1' }, + { id: 2, name: 'a2' }, + { id: 3, name: 'a3' }, + ], + }); + Mp.conditions = [[1, 2], [1, 3]]; + expect(Mp._variants().conditions).toEqual([ + [{ id: 1, name: 'a1' }, { id: 2, name: 'a2' }], + [{ id: 1, name: 'a1' }, { id: 3, name: 'a3' }], + ]); + }); + + test('tests cases that should throw an Error', () => { + const molecularProfile = { + id: 123, + variants: [ + { id: 1, name: 'a1' }, + { id: 2, name: 'a2' }, + ], + }; + const Mp = MolecularProfile(molecularProfile); + Mp.conditions = [[1, 2], [1, 3]]; + expect(() => Mp._variants()).toThrow( + `unable to process molecular profile with missing or misformatted variants (${molecularProfile.id || ''})`, + ); + }); +}); + +describe('MolecularProfile.process()', () => { + test('gene infos not interfering', () => { + expect(MolecularProfile({ + parsedName: [{ __typename: 'Feature' }, { id: 1 }], + variants: [{ id: 1, name: 'a1' }], + }).process().conditions).toEqual([[{ id: 1, name: 'a1' }]]); + }); + + test.each([ + [{}], + [{ parsedName: '' }], + [{ parsedName: [] }], + [{ parsedName: [''] }], + ])( + 'tests cases that should throw an Error', (molecularProfile) => { + expect(() => MolecularProfile(molecularProfile).process()).toThrow( + `unable to process molecular profile with missing or misformatted parsedName (${molecularProfile.id || ''})`, + ); + }, + ); + + test('not providing a molecularProfile argument should also throw an Error', () => { + expect(() => MolecularProfile().process()).toThrow( + 'unable to process molecular profile with missing or misformatted parsedName ()', + ); + }); + + test('test case that should throw a NotImplementedError', () => { + const molecularProfile = { + id: 1, + parsedName: [ + { __typename: 'Feature' }, { id: 1 }, { text: 'AND' }, { text: 'NOT' }, { text: '(' }, + { __typename: 'Feature' }, { id: 2 }, { text: 'OR' }, { __typename: 'Feature' }, { id: 3 }, { text: ')' }, + ], + }; + expect(() => MolecularProfile(molecularProfile).process()).toThrow( + `unable to process molecular profile with NOT operator (${molecularProfile.id || ''})`, + ); + }); +}); diff --git a/test/civic.publication.test.js b/test/civic/civic.publication.test.js similarity index 94% rename from test/civic.publication.test.js rename to test/civic/civic.publication.test.js index fde748dd..1445ec72 100644 --- a/test/civic.publication.test.js +++ b/test/civic/civic.publication.test.js @@ -1,4 +1,4 @@ -const { titlesMatch } = require('../src/civic/publication'); +const { titlesMatch } = require('../../src/civic/publication'); describe('titlesMatch', () => { diff --git a/test/civic/civic.relevance.test.js b/test/civic/civic.relevance.test.js new file mode 100644 index 00000000..bb922dbc --- /dev/null +++ b/test/civic/civic.relevance.test.js @@ -0,0 +1,88 @@ +const { translateRelevance } = require('../../src/civic/relevance'); + +describe('translateRelevance', () => { + test.each([ + ['DOES_NOT_SUPPORT', 'FUNCTIONAL', 'DOMINANT_NEGATIVE', 'no dominant negative'], + ['DOES_NOT_SUPPORT', 'FUNCTIONAL', 'GAIN_OF_FUNCTION', 'no gain of function'], + ['DOES_NOT_SUPPORT', 'FUNCTIONAL', 'NEOMORPHIC', 'no neomorphic'], + ['DOES_NOT_SUPPORT', 'ONCOGENIC', 'ONCOGENICITY', 'likely benign'], + ['DOES_NOT_SUPPORT', 'PREDICTIVE', 'RESISTANCE', 'no resistance'], + ['DOES_NOT_SUPPORT', 'PREDICTIVE', 'SENSITIVITYRESPONSE', 'no response'], + ['SUPPORTS', 'DIAGNOSTIC', 'NEGATIVE', 'opposes diagnosis'], + ['SUPPORTS', 'DIAGNOSTIC', 'POSITIVE', 'favours diagnosis'], + ['SUPPORTS', 'FUNCTIONAL', 'DOMINANT_NEGATIVE', 'dominant negative'], + ['SUPPORTS', 'FUNCTIONAL', 'GAIN_OF_FUNCTION', 'gain of function'], + ['SUPPORTS', 'FUNCTIONAL', 'LOSS_OF_FUNCTION', 'loss of function'], + ['SUPPORTS', 'FUNCTIONAL', 'NEOMORPHIC', 'neomorphic'], + ['SUPPORTS', 'FUNCTIONAL', 'UNALTERED_FUNCTION', 'unaltered function'], + ['SUPPORTS', 'ONCOGENIC', 'ONCOGENICITY', 'likely oncogenic'], + ['SUPPORTS', 'PREDICTIVE', 'ADVERSE_RESPONSE', 'adverse response'], + ['SUPPORTS', 'PREDICTIVE', 'REDUCED_SENSITIVITY', 'reduced sensitivity'], + ['SUPPORTS', 'PREDICTIVE', 'RESISTANCE', 'resistance'], + ['SUPPORTS', 'PREDICTIVE', 'SENSITIVITYRESPONSE', 'sensitivity'], + ['SUPPORTS', 'PREDISPOSING', 'LIKELY_PATHOGENIC', 'likely pathogenic'], + ['SUPPORTS', 'PREDISPOSING', 'PATHOGENIC', 'pathogenic'], + ['SUPPORTS', 'PREDISPOSING', 'POSITIVE', 'predisposing'], + ['SUPPORTS', 'PREDISPOSING', 'PREDISPOSITION', 'likely predisposing'], + ['SUPPORTS', 'PREDISPOSING', 'PROTECTIVENESS', 'likely protective'], + ['SUPPORTS', 'PREDISPOSING', 'UNCERTAIN_SIGNIFICANCE', 'likely predisposing'], + ['SUPPORTS', 'PROGNOSTIC', 'BETTER_OUTCOME', 'favourable prognosis'], + ['SUPPORTS', 'PROGNOSTIC', 'POOR_OUTCOME', 'unfavourable prognosis'], + ['NA', 'ONCOGENIC', 'NA', 'likely oncogenic'], + ['NA', 'PREDISPOSING', 'NA', 'likely predisposing'], + ])( + '%s|%s|%s returns %s', (evidenceDirection, evidenceType, clinicalSignificance, expected) => { + expect(translateRelevance( + evidenceType, + evidenceDirection, + clinicalSignificance, + )).toEqual(expected); + }, + ); + + test.each([ + // Test cases that should throw an error + ['DOES_NOT_SUPPORT', 'DIAGNOSTIC', 'POSITIVE'], + ['DOES_NOT_SUPPORT', 'DIAGNOSTIC', 'NEGATIVE'], + ['DOES_NOT_SUPPORT', 'DIAGNOSTIC', '--'], + ['DOES_NOT_SUPPORT', 'FUNCTIONAL', 'LOSS_OF_FUNCTION'], + ['DOES_NOT_SUPPORT', 'FUNCTIONAL', 'UNALTERED_FUNCTION'], + ['DOES_NOT_SUPPORT', 'FUNCTIONAL', 'UNKNOWNED'], + ['DOES_NOT_SUPPORT', 'FUNCTIONAL', '--'], + ['DOES_NOT_SUPPORT', 'ONCOGENIC', 'PROTECTIVENESS'], + ['DOES_NOT_SUPPORT', 'ONCOGENIC', '--'], + ['DOES_NOT_SUPPORT', 'PREDICTIVE', 'ADVERSE_RESPONSE'], + ['DOES_NOT_SUPPORT', 'PREDICTIVE', 'NA'], + ['DOES_NOT_SUPPORT', 'PREDICTIVE', 'REDUCED_SENSITIVITY'], + ['DOES_NOT_SUPPORT', 'PREDICTIVE', '--'], + ['DOES_NOT_SUPPORT', 'PREDISPOSING', 'PREDISPOSITION'], + ['DOES_NOT_SUPPORT', 'PREDISPOSING', 'PROTECTIVENESS'], + ['DOES_NOT_SUPPORT', 'PREDISPOSING', '--'], + ['DOES_NOT_SUPPORT', 'PROGNOSTIC', 'BETTER_OUTCOME'], + ['DOES_NOT_SUPPORT', 'PROGNOSTIC', 'NA'], + ['DOES_NOT_SUPPORT', 'PROGNOSTIC', 'POOR_OUTCOME'], + ['DOES_NOT_SUPPORT', 'PROGNOSTIC', '--'], + ['DOES_NOT_SUPPORT', '--', '--'], + ['SUPPORTS', 'DIAGNOSTIC', '--'], + ['SUPPORTS', 'FUNCTIONAL', 'UNKNOWNED'], + ['SUPPORTS', 'FUNCTIONAL', '--'], + ['SUPPORTS', 'ONCOGENIC', 'PROTECTIVENESS'], + ['SUPPORTS', 'ONCOGENIC', '--'], + ['SUPPORTS', 'PREDICTIVE', 'NA'], + ['SUPPORTS', 'PREDICTIVE', '--'], + ['SUPPORTS', 'PREDISPOSING', '--'], + ['SUPPORTS', 'PROGNOSTIC', 'NA'], + ['SUPPORTS', 'PROGNOSTIC', '--'], + ['SUPPORTS', '--', '--'], + ['NA', '--', '--'], + ['--', '--', '--'], + ])( + '%s|%s|%s errors', (evidenceDirection, evidenceType, clinicalSignificance) => { + expect(() => translateRelevance( + evidenceType, + evidenceDirection, + clinicalSignificance, + )).toThrow('unable to process relevance'); + }, + ); +}); diff --git a/test/civic/civic.statement.test.js b/test/civic/civic.statement.test.js new file mode 100644 index 00000000..a74d1269 --- /dev/null +++ b/test/civic/civic.statement.test.js @@ -0,0 +1,167 @@ +/* eslint-disable jest/no-disabled-tests */ +const { + contentMatching, + isMatching, + needsUpdate, +} = require('../../src/civic/statement'); + + +// Generic content +const content = { + conditions: ['#123:1', '#123:2'], // conditions NEEDS to be already sorted in ascending order + description: 'test', + evidence: ['#123:1'], + evidenceLevel: ['#123:1'], + relevance: '#123:1', + reviewStatus: 'not required', + source: '#123:1', + sourceId: '9999', + subject: '#123:1', +}; + +// Combination of matching and not matching content +const allFromCivic = [ + { ...content, subject: '#888:0' }, // matching with allFromGkb[3] + { ...content, subject: '#888:1' }, // matching with allFromGkb[1] + { ...content, subject: '#888:2' }, // not matching +]; +const allFromGkb = [ + { ...content, '@rid': '#999:0', subject: '#888:3' }, // not matching + { ...content, '@rid': '#999:1', subject: '#888:1' }, // matching with allFromCivic[1] + { ...content, '@rid': '#999:2', subject: '#888:4' }, // not matching + { ...content, '@rid': '#999:3', subject: '#888:0' }, // matching with allFromCivic[0] +]; + +describe('needsUpdate', () => { + // No need to update + test('identical content', () => { + expect(needsUpdate({ + fromCivic: content, + fromGkb: content, + })).toBe(false); + }); + + test('discarding gkb rid', () => { + expect(needsUpdate({ + fromCivic: content, + fromGkb: { ...content, '@rid': '#123:1' }, + })).toBe(false); + }); + + // Need to update + test('any difference', () => { + expect(needsUpdate({ + fromCivic: content, + fromGkb: { ...content, description: '' }, + })).toBe(true); + }); +}); + +describe('isMatching', () => { + // No matching + test('difference on conditions', () => { + expect(isMatching({ + fromCivic: content, + fromGkb: { ...content, conditions: ['#123:1', '#123:3'] }, + })).toBe(false); + }); + + test('difference on subject', () => { + expect(isMatching({ + fromCivic: content, + fromGkb: { ...content, subject: '#123:2' }, + })).toBe(false); + }); + + // Matching + test('difference on conditions while matching only on subject', () => { + expect(isMatching({ + fromCivic: content, + fromGkb: { ...content, conditions: ['#123:1', '#123:3'] }, + p: ['subject'], + })).toBe(true); + }); + + // Matching on subject alone + test('any other difference', () => { + expect(isMatching({ + fromCivic: content, + fromGkb: { ...content, description: '' }, + })).toBe(true); + }); +}); + +describe('contentMatching', () => { + test('matching only on conditions and subject', () => { + const records = contentMatching({ + allFromCivic, + allFromGkb, + matchingOnSubjectAlone: false, + }); + + // matching content + expect(records.toUpdate.length).toBe(2); + + // allFromGkb with no matches + expect(records.toDelete.length).toBe(2); + + // allFromCivic with no matches + expect(records.toCreate.length).toBe(1); + + // matching content + expect(records.toUpdate[0]).toEqual({ + fromCivic: allFromCivic[0], + fromGkb: allFromGkb[3], + }); + expect(records.toUpdate[1]).toEqual({ + fromCivic: allFromCivic[1], + fromGkb: allFromGkb[1], + }); + + // allFromGkb with no matches + expect(records.toDelete[0]).toEqual(allFromGkb[0]); + expect(records.toDelete[1]).toEqual(allFromGkb[2]); + + // allFromCivic with no matches + expect(records.toCreate[0]).toEqual(allFromCivic[2]); + }); + + test('matching also on subject alone, without artificial matching', () => { + const records = contentMatching({ + allFromCivic: [ + { ...content, conditions: ['#777:77'], subject: '#777:1' }, + { ...content, conditions: ['#777:77'], subject: '#777:2' }, + ], + allFromGkb: [ + { ...content, conditions: ['#888:88'], subject: '#777:1' }, + { ...content, conditions: ['#888:88'], subject: '#888:2' }, + ], + matchingWithoutComparing: false, + }); + + // matching content + expect(records.toUpdate.length).toBe(1); + + // allFromGkb with no matches + expect(records.toDelete.length).toBe(1); + + // allFromCivic with no matches + expect(records.toCreate.length).toBe(1); + }); + + test('matching until artificial matching', () => { + const records = contentMatching({ + allFromCivic, + allFromGkb, + }); + + // matching content + expect(records.toUpdate.length).toBe(3); + + // allFromGkb with no matches + expect(records.toDelete.length).toBe(1); + + // allFromCivic with no matches + expect(records.toCreate.length).toBe(0); + }); +}); diff --git a/test/civic.test.js b/test/civic/civic.variant.test.js similarity index 87% rename from test/civic.test.js rename to test/civic/civic.variant.test.js index ef3319d4..4814157c 100644 --- a/test/civic.test.js +++ b/test/civic/civic.variant.test.js @@ -1,5 +1,5 @@ -const { normalizeVariantRecord } = require('../src/civic/variant'); -const { translateRelevance } = require('../src/civic'); +/* eslint-disable jest/no-disabled-tests */ +const { normalizeVariantRecord } = require('../../src/civic/variant'); describe('normalizeVariantRecord', () => { test('exon mutation', () => { @@ -219,7 +219,6 @@ describe('normalizeVariantRecord', () => { ]); }); - test('categorical variant with spaces', () => { const variants = normalizeVariantRecord({ entrezId: 1, @@ -335,7 +334,6 @@ describe('normalizeVariantRecord', () => { ]); }); - test('cds notation', () => { // BCR-ABL const variants = normalizeVariantRecord({ @@ -522,7 +520,6 @@ describe('normalizeVariantRecord', () => { ]); }); - test('protein dup with cds dup', () => { // p.s193_c196dupstsc (c.577_588dupagcaccagctgc) const variants = normalizeVariantRecord({ @@ -585,7 +582,7 @@ describe('normalizeVariantRecord', () => { ]); }); - test('catalogue variant', () => { + test.skip('catalogue variant', () => { // RS3910384 }); @@ -621,11 +618,11 @@ describe('normalizeVariantRecord', () => { ]); }); - test('duplicate fusion', () => { + test.skip('duplicate fusion', () => { // AGGF1-PDGFRB, AGGF1-PDGFRB C843G }); - test('non-specific positional mutaiton', () => { + test.skip('non-specific positional mutaiton', () => { // E1813 mutations }); @@ -658,46 +655,3 @@ describe('normalizeVariantRecord', () => { }); }); }); - -describe('translateRelevance', () => { - test.each([ - ['PREDICTIVE', 'SUPPORTS', 'ADVERSE_RESPONSE', 'adverse response'], - ['PREDICTIVE', 'SUPPORTS', 'REDUCED_SENSITIVITY', 'reduced sensitivity'], - ['PREDICTIVE', 'SUPPORTS', 'RESISTANCE', 'resistance'], - ['PREDICTIVE', 'SUPPORTS', 'SENSITIVITYRESPONSE', 'sensitivity'], - ['DIAGNOSTIC', 'SUPPORTS', 'POSITIVE', 'favours diagnosis'], - ['DIAGNOSTIC', 'SUPPORTS', 'NEGATIVE', 'opposes diagnosis'], - ['PROGNOSTIC', 'SUPPORTS', 'NEGATIVE', 'unfavourable prognosis'], - ['PROGNOSTIC', 'SUPPORTS', 'POOR_OUTCOME', 'unfavourable prognosis'], - ['PROGNOSTIC', 'SUPPORTS', 'POSITIVE', 'favourable prognosis'], - ['PROGNOSTIC', 'SUPPORTS', 'BETTER_OUTCOME', 'favourable prognosis'], - ['PREDISPOSING', 'SUPPORTS', 'POSITIVE', 'predisposing'], - ['PREDISPOSING', 'SUPPORTS', null, 'predisposing'], - ['PREDISPOSING', 'SUPPORTS', 'PATHOGENIC', 'pathogenic'], - ['PREDISPOSING', 'SUPPORTS', 'LIKELY_PATHOGENIC', 'likely pathogenic'], - ['FUNCTIONAL', 'SUPPORTS', 'GAIN_OF_FUNCTION', 'gain of function'], - ['PREDICTIVE', 'DOES_NOT_SUPPORT', 'SENSITIVITYRESPONSE', 'no response'], - ['PREDICTIVE', 'DOES_NOT_SUPPORT', 'RESISTANCE', 'no resistance'], - ['FUNCTIONAL', 'SUPPORTS', 'NEOMORPHIC', 'neomorphic'], - ['FUNCTIONAL', 'SUPPORTS', 'UNALTERED_FUNCTION', 'unaltered function'], - ])( - '%s|%s|%s returns %s', (evidenceType, evidenceDirection, clinicalSignificance, expected) => { - expect(translateRelevance(evidenceType, evidenceDirection, clinicalSignificance)).toEqual(expected); - }, - ); - - test.each([ - // For EvType-EvDir-ClinSign test cases that should not be loaded - ['PROGNOSTIC', 'DOES_NOT_SUPPORT', 'POOR_OUTCOME'], - ['FUNCTIONAL', 'DOES_NOT_SUPPORT', 'NEOMORPHIC'], - ['PREDISPOSING', 'DOES_NOT_SUPPORT', 'POSITIVE'], - ['PREDISPOSING', 'NA', 'NA'], - ['DIAGNOSTIC', 'DOES_NOT_SUPPORT', 'POSITIVE'], - ['DIAGNOSTIC', 'DOES_NOT_SUPPORT', 'NEGATIVE'], - ['FUNCTIONAL', 'DOES_NOT_SUPPORT', 'UNALTERED_FUNCTION'], - ])( - '%s|%s|%s errors', (evidenceType, evidenceDirection, clinicalSignificance) => { - expect(() => translateRelevance(evidenceType, evidenceDirection, clinicalSignificance)).toThrow('unable to process relevance'); - }, - ); -}); diff --git a/test/data/clinicaltrialsgov.NCT03478891.json b/test/data/clinicaltrialsgov.NCT03478891.json new file mode 100644 index 00000000..e55bdc69 --- /dev/null +++ b/test/data/clinicaltrialsgov.NCT03478891.json @@ -0,0 +1,3958 @@ +{ + "protocolSection": { + "identificationModule": { + "nctId": "NCT03478891", + "orgStudyIdInfo": { + "id": "180069" + }, + "secondaryIdInfos": [ + { + "id": "18-I-0069" + } + ], + "organization": { + "fullName": "National Institutes of Health Clinical Center (CC)", + "class": "NIH" + }, + "briefTitle": "Safety and Pharmacokinetics of a Human Monoclonal Antibody, VRC-EBOMAB092-00-AB (MAb114), Administered Intravenously to Healthy Adults", + "officialTitle": "VRC 608: A Phase I, Open-Label, Dose-Escalation Study of the Safety and Pharmacokinetics of a Human Monoclonal Antibody, VRC-EBOMAB092-00-AB (MAb114), Administered Intravenously to Healthy Adults" + }, + "statusModule": { + "statusVerifiedDate": "2020-10", + "overallStatus": "COMPLETED", + "expandedAccessInfo": { + "hasExpandedAccess": true + }, + "startDateStruct": { + "date": "2018-05-16", + "type": "ACTUAL" + }, + "primaryCompletionDateStruct": { + "date": "2019-03-20", + "type": "ACTUAL" + }, + "completionDateStruct": { + "date": "2019-03-20", + "type": "ACTUAL" + }, + "studyFirstSubmitDate": "2018-03-21", + "studyFirstSubmitQcDate": "2018-03-24", + "studyFirstPostDateStruct": { + "date": "2018-03-27", + "type": "ACTUAL" + }, + "resultsFirstSubmitDate": "2018-10-09", + "resultsFirstSubmitQcDate": "2018-10-09", + "resultsFirstPostDateStruct": { + "date": "2018-10-11", + "type": "ACTUAL" + }, + "lastUpdateSubmitDate": "2020-10-21", + "lastUpdatePostDateStruct": { + "date": "2020-10-26", + "type": "ACTUAL" + } + }, + "sponsorCollaboratorsModule": { + "responsibleParty": { + "type": "SPONSOR" + }, + "leadSponsor": { + "name": "National Institute of Allergy and Infectious Diseases (NIAID)", + "class": "NIH" + } + }, + "oversightModule": { + "oversightHasDmc": false, + "isFdaRegulatedDrug": true, + "isFdaRegulatedDevice": false + }, + "descriptionModule": { + "briefSummary": "Background:\n\nEbola is a virus that has infected and killed people mostly in West Africa. There is no treatment or prevention for it, but several drugs are being studied. Researchers want to test the drug MAb114 in healthy people not exposed to Ebola to see whether it can be used for Ebola treatment in people who are infected in the future. This trial will not expose volunteers to the Ebola virus.\n\nObjectives:\n\nTo see if MAb114 is safe and how a person's body responds to it.\n\nEligibility:\n\nHealthy adults ages 18-60 who weigh 220.5 pounds or less\n\nDesign:\n\nParticipants will be screened under protocol NIH 11-I-0164 with:\n\n* Medical history\n* Physical exam\n* Blood or urine tests\n\nParticipants will have a first 8- to10-hour visit. They will get MAb114 by IV infusion. For this, a thin tube will be placed in an arm vein. They may get an IV line in their other arm to collect blood. Blood will be taken many times before and after the infusion. Participants may have a urine test.\n\nParticipants will get a thermometer to check their temperature for 3 days after they get MAb114. They will record their highest temperature and any symptoms.\n\nParticipants will have about 14 more study visits over 6 months. At each visit, they will have blood taken and be checked for any health changes. They will talk about how they are feeling and if they have taken any medications.\n\nAt the end of the 6 months, participants may be invited to take part in another study for follow-up sample collection.", + "detailedDescription": "VRC 608: A Phase I, Open-Label, Dose-Escalation Study of the Safety and Pharmacokinetics of a Human Monoclonal Antibody, VRC-EBOMAB092-00-AB (MAb114), Administered Intravenously to Healthy Adults\n\nStudy Design: VRC 608 is the first-in-human Phase I study to examine safety, tolerability and pharmacokinetics of the monoclonal antibody (MAb), VRC-EBOMAB092-00-AB (MAb114). MAb114 will be administered as a single dose. The hypotheses are: 1) MAb114 administration to healthy adults will be safe by the intravenous (IV) route; and 2) MAb114 will be detectable in human sera with a definable half-life. The primary objectives are to evaluate the safety and tolerability of MAb114 in healthy adults. Secondary objectives will evaluate the pharmacokinetics of MAb114 and the potential to detect an anti-drug antibody response to MAb114.\n\nProduct Description: MAb114 is a human IgG1 MAb targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP). It was developed by the VRC/NIAID/NIH and manufactured at Cook Pharmica LLC d.b.a. Catalent Indiana, LLC (Bloomington, IN) under current Good Manufacturing Practice (cGMP) regulations. MAb114 is supplied as a lyophilized product in a glass vial at 400 mg per vial with target overfill to 425 mg per vial.\n\nSubjects: Up to 30 healthy adults, 18-60 years of age.\n\nStudy Plan: This is an open-label, dose-escalation study of MAb114 administered by IV infusion at dosages of 5, 25 and 50 mg/kg (Groups 1-3). Enrollment will begin with the lowest dose group. Following the first product administration in each group, the study team will wait at least 3 days before administering MAb114 to a second subject within the same group. Dose-escalation evaluations will occur to ensure the safety data support proceeding to the higher doses. Solicited reactogenicity following product administration will be evaluated using a 3-day diary. Assessment of safety will include clinical observation and monitoring of serum hematological and chemical parameters at defined timepoints throughout the study.\n\n* Group 1: 3 subjects; 5 mg/kg IV\n* Group 2: 5 subjects; 25 mg/kg IV\n* Group 3: 10 subjects; 50 mg/kg IV\n* Total\\* 18 subjects\n\n * A minimum of 18 subjects will be enrolled. Enrollment up to a total of 30 subjects is permitted if additional subjects are necessary for safety or pharmacokinetic (PK) evaluations.\n\nStudy Duration: Subjects will be followed for 24 weeks after the study product administration." + }, + "conditionsModule": { + "conditions": [ + "Healthy Adult Immune Responses to Vaccine" + ], + "keywords": [ + "Ebola Virus", + "Immune Response", + "Filovirus", + "Ebola Hemorrhagic Fever", + "First in Human" + ] + }, + "designModule": { + "studyType": "INTERVENTIONAL", + "phases": [ + "PHASE1" + ], + "designInfo": { + "allocation": "NON_RANDOMIZED", + "interventionModel": "SEQUENTIAL", + "primaryPurpose": "TREATMENT", + "maskingInfo": { + "masking": "NONE" + } + }, + "enrollmentInfo": { + "count": 19, + "type": "ACTUAL" + } + }, + "armsInterventionsModule": { + "armGroups": [ + { + "label": "Group 1: 5 mg/kg IV", + "type": "EXPERIMENTAL", + "description": "Group 1 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 5 mg/kg.", + "interventionNames": [ + "Biological: VRC-EBOMAB092-00-AB (MAb114)" + ] + }, + { + "label": "Group 2: 25 mg/kg IV", + "type": "EXPERIMENTAL", + "description": "Group 2 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 25 mg/kg.", + "interventionNames": [ + "Biological: VRC-EBOMAB092-00-AB (MAb114)" + ] + }, + { + "label": "Group 3: 50 mg/kg IV", + "type": "EXPERIMENTAL", + "description": "Group 3 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 50 mg/kg.", + "interventionNames": [ + "Biological: VRC-EBOMAB092-00-AB (MAb114)" + ] + } + ], + "interventions": [ + { + "type": "BIOLOGICAL", + "name": "VRC-EBOMAB092-00-AB (MAb114)", + "description": "VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP).", + "armGroupLabels": [ + "Group 1: 5 mg/kg IV", + "Group 2: 25 mg/kg IV", + "Group 3: 50 mg/kg IV" + ] + } + ] + }, + "outcomesModule": { + "primaryOutcomes": [ + { + "measure": "Number of Subjects Experiencing Infusion Reaction During Product Administration", + "description": "Possible infusion reaction symptoms: unusually tired/feeling unwell, muscles aches, headache, chills, rigors, nausea, fever, joint pain, urticaria/rash, and pruritus. Also information was collected if administration was slowed down or stopped for reactogenicity reasons.", + "timeFrame": "About 30 minutes of product administration" + }, + { + "measure": "Number of Subjects Reporting Systemic Reactogenicity Signs and Symptoms Within 3 Days of Product Administration", + "description": "Subjects recorded 3-day systemic reactogenicity symptoms in a diary after study product administration. Solicited systemic symptoms include: unusually tired/feeling unwell, muscles aches, headache, chills, nausea, fever and joint pain. Subjects recorded highest measured temperature daily. Clinicians reviewed the diary with the subject and collected resolution information for any symptoms that were not resolved within 3 days. Subjects were counted once for each symptom at the worst severity if they indicated experiencing the symptom at any severity during the reporting period. The number reported for \"Any Systemic Symptom\" is the number of subjects experiencing any systemic symptom as reported at the worst severity. Solicited reactogenicity was recorded without an attribution assessment. Grading was done by Division of AIDS Table for Grading the Severity of Adult and Pediatric Adverse Events Version 2.1.", + "timeFrame": "3 days after product administration" + }, + { + "measure": "Number of Subjects Reporting Local Reactogenicity Signs and Symptoms Within 7 Days of Product Administration", + "description": "Local reactogenicity symptoms were assessed and recorded by clinicians. Solicited local symptoms include pain/tenderness, swelling, redness, bruising, and pruritus (itchiness) at the product administration site. Clinicians assessed the study product administration site for local symptoms on the day of product administration after completion of the administration and on Days 1, 2 and 7 post administration. Subjects were counted once for each symptom at the worst severity if they experienced the symptom at any severity during the reporting period. If symptoms were experienced, clinicians collected resolution information for any symptom that wasn't resolved within 7 days. The number reported for \"Any Local Symptom\" is the number of subjects reporting any local symptom as reported at the worst severity. Solicited reactogenicity was recorded without attribution assessment. Grading was done by Division of AIDS Table for Grading the Severity of Adult and Pediatric Adverse Events Version 2.1.", + "timeFrame": "7 days after product administration" + }, + { + "measure": "Number of Subjects Reporting 1 or More Unsolicited Non-Serious Adverse Events", + "description": "Unsolicited adverse events (AEs) collected during the period from study product administration at Day 0 through 28 days after product administration. After the indicated time period through the last expected study visit at 24 weeks after product administration, only new chronic medical conditions collected as unsolicited AEs.\n\nThe number reported is the number of subjects who experienced at least one AE in the reporting period. A subject with multiple experiences of the same event is counted once using the event of worst severity. The relationship between an AE and the product was assessed by the investigator on the basis of his or her clinical judgment and the protocol-specific definitions of Related - There is a reasonable possibility that the AE may be related to the study product and Not Related - There is not a reasonable possibility that the AE is related to the study product.", + "timeFrame": "Through 24 weeks after product administration" + }, + { + "measure": "Number of Subjects Reporting Serious Adverse Events", + "description": "Serious adverse events (SAEs) collected during the period from study product administration at Day 0 through 24 weeks after product administration. Grading done by Division of AIDS Table for Grading the Severity of Adult and Pediatric Adverse Events Version 2.1. The relationship between a SAE and the product was assessed by the investigator on the basis of his or her clinical judgment and the protocol-specific definitions of Related - There is a reasonable possibility that the SAE may be related to the study product and Not Related - There is not a reasonable possibility that the SAE is related to the study product.", + "timeFrame": "Through 24 weeks after product administration" + } + ], + "secondaryOutcomes": [ + { + "measure": "Maximum Observed Serum Concentration (Cmax) of MAb114", + "description": "Cmax is the peak serum concentration that MAb114 achieves after it has been administered; it is determined as a maximum value on the summary pharmacokinetic (PK) curve for each study group.", + "timeFrame": "Pre-infusion (baseline), end of infusion (0h), 1 hr, 3 hr, 6 hr, 24 hr, 48 hr and 7-28 days post-infusion" + }, + { + "measure": "Time to Reach Maximum Observed Serum Concentration (Tmax) of MAb114", + "description": "Tmax is the time it takes to reach Cmax of MAb114 after it has been administered; it is determined based on the summary PK curve for each study group.", + "timeFrame": "Pre-infusion (baseline), end of infusion (0h), 1 hr, 3 hr, 6 hr, 24 hr, 48 hr and 7-28 days post-infusion" + }, + { + "measure": "Mean Serum Concentration of MAb114", + "description": "The mean of individual subject MAb114 serum concentrations by administered dose group", + "timeFrame": "Pre-infusion (baseline), end of infusion (0h), 1 hr, 3 hr, 6 hr, 24 hr, 48 hr and 7-28 days post-infusion" + }, + { + "measure": "Overall IV Half-life (T1/2) of MAb114", + "description": "Half-life (T1/2) is the time required for half of the drug to be eliminated from the serum.", + "timeFrame": "Pre-infusion (baseline), end of infusion (0h), 1 hr, 3 hr, 6 hr, 24 hr, 48 hr and 7-56 days post-infusion" + }, + { + "measure": "Area Under the Curve (AUC0-28D)", + "description": "The AUC0-28D represents the total drug exposure in 28 days after MAb114 administration; it is determined based on the summary PK curve for each group.", + "timeFrame": "Pre-infusion (baseline), end of infusion (0h), 1 hr, 3 hr, 6 hr, 24 hr, 48 hr and 7-28 days post-infusion" + }, + { + "measure": "Volume of Distribution (Vd) at Steady-state", + "description": "Theoretical volume that would be necessary to contain the total amount of administered drug at the same concentration as observed in plasma. It represents the degree to which a drug is distributed in body tissue rather than the plasma and calculated based in the PK curve for each study group.", + "timeFrame": "Pre-infusion (baseline), end of infusion (0h), 1 hr, 3 hr, 6 hr, 24 hr, 48 hr and 7-28 days post-infusion" + }, + { + "measure": "MAb114 Clearance Rate", + "description": "Rate of MAb114 elimination divided by the plasma MAb114 concentration; determined based on the summary PK curve for each study group.", + "timeFrame": "Pre-infusion (baseline), end of infusion (0h), 1 hr, 3 hr, 6 hr, 24 hr, 48 hr and 7-28 days post-infusion" + }, + { + "measure": "Number of Subjects Who Produced Anti-drug Antibodies to MAb114", + "description": "Serum samples collected 28 days and 56 days after MAb114 administration", + "timeFrame": "Days 28 and 56 post-infusion" + } + ] + }, + "eligibilityModule": { + "eligibilityCriteria": "INCLUSION CRITERIA:\n\nA volunteer must meet all of the following criteria:\n\n* Able and willing to complete the informed consent process.\n* Available for clinical follow-up through the last study visit.\n* 18 to 60 years of age.\n* In good general health without clinically significant medical history.\n* Willing to have blood samples collected, stored indefinitely, and used for research purposes.\n* Able to provide proof of identity to the satisfaction of the study clinician completing the enrollment process.\n* Physical examination without clinically significant findings within the 84 days prior to enrollment.\n* Have screening laboratory values within 84 days prior to enrollment that meet the following criteria:\n\n * White Blood Cell (WBC) 2,500-12,000/mm\\^3\n * WBC differential either within institutional normal range or accompanied by the Principal Investigator (PI) or designee approval\n * Platelets = 125,000 - 400,000/mm\\^3\n * Hemoglobin within institutional normal range or accompanied by the PI or designee approval\n * Creatinine less than or equal to 1.1 x upper limit of normal (ULN)\n * Alanine aminotransferase (ALT) less than or equal to 1.25 x ULN\n * Negative for human immunodeficiency virus (HIV) infection by a Food and Drug Administration (FDA) approved method of detection\n * Negative for Hepatitis B core antibody (HBcAb) and Hepatitis C virus antibody (HCV Ab)\n* Criteria applicable to women of childbearing potential:\n\n * If a woman is of reproductive potential and sexually active with a male partner, then she agrees to use an effective means of birth control from the time of study enrollment until the last study visit, or to be monogamous with a partner who has had a vasectomy.\n * Negative human chorionic gonadotropin (beta-HCG) pregnancy test (urine or serum) on day of enrollment and product administration.\n\nEXCLUSION CRITERIA:\n\nA volunteer will be excluded from study participation if one or more of the following conditions apply:\n\n* Previous receipt of a licensed or investigational monoclonal antibody or Ebola vaccine.\n* Weight \\>100 kg.\n* Any history of a severe allergic reaction with generalized urticaria, angioedema or anaphylaxis prior to enrollment that has a reasonable risk of recurrence during the study.\n* Hypertension that is not well controlled.\n* Woman who is breast-feeding, or planning to become pregnant during study participation.\n* Receipt of any investigational study product within 28 days prior to enrollment.\n* Any other chronic or clinically significant medical condition that in the opinion of the investigator would jeopardize the safety or rights of the volunteer, including but not limited to: diabetes mellitus type I, chronic hepatitis; OR clinically significant forms of: drug or alcohol abuse, asthma, autoimmune disease, psychiatric disorders, heart disease, or cancer.\n* Bleeding disorder diagnosed by a doctor (e.g. factor deficiency, coagulopathy, or platelet disorder requiring special precautions) or significant bruising or bleeding difficulties with intramuscular (IM) injections or blood draws.\n* Use of angiotensin-converting enzyme (ACE) inhibitors or other potential nephrotoxins.", + "healthyVolunteers": true, + "sex": "ALL", + "minimumAge": "18 Years", + "maximumAge": "60 Years", + "stdAges": [ + "ADULT" + ] + }, + "contactsLocationsModule": { + "overallOfficials": [ + { + "name": "Martin R Gaudinski, M.D.", + "affiliation": "National Institute of Allergy and Infectious Diseases (NIAID)", + "role": "PRINCIPAL_INVESTIGATOR" + } + ], + "locations": [ + { + "facility": "National Institutes of Health Clinical Center", + "city": "Bethesda", + "state": "Maryland", + "zip": "20892", + "country": "United States", + "geoPoint": { + "lat": 38.98067, + "lon": -77.10026 + } + } + ] + }, + "referencesModule": { + "references": [ + { + "pmid": "26917593", + "type": "BACKGROUND", + "citation": "Corti D, Misasi J, Mulangu S, Stanley DA, Kanekiyo M, Wollen S, Ploquin A, Doria-Rose NA, Staupe RP, Bailey M, Shi W, Choe M, Marcus H, Thompson EA, Cagigi A, Silacci C, Fernandez-Rodriguez B, Perez L, Sallusto F, Vanzetta F, Agatic G, Cameroni E, Kisalu N, Gordon I, Ledgerwood JE, Mascola JR, Graham BS, Muyembe-Tamfun JJ, Trefry JC, Lanzavecchia A, Sullivan NJ. Protective monotherapy against lethal Ebola virus infection by a potently neutralizing antibody. Science. 2016 Mar 18;351(6279):1339-42. doi: 10.1126/science.aad5224. Epub 2016 Feb 25." + }, + { + "pmid": "26917592", + "type": "BACKGROUND", + "citation": "Misasi J, Gilman MS, Kanekiyo M, Gui M, Cagigi A, Mulangu S, Corti D, Ledgerwood JE, Lanzavecchia A, Cunningham J, Muyembe-Tamfun JJ, Baxa U, Graham BS, Xiang Y, Sullivan NJ, McLellan JS. Structural and molecular basis for Ebola virus neutralization by protective human antibodies. Science. 2016 Mar 18;351(6279):1343-6. doi: 10.1126/science.aad6117. Epub 2016 Feb 25." + }, + { + "pmid": "30686586", + "type": "RESULT", + "citation": "Gaudinski MR, Coates EE, Novik L, Widge A, Houser KV, Burch E, Holman LA, Gordon IJ, Chen GL, Carter C, Nason M, Sitar S, Yamshchikov G, Berkowitz N, Andrews C, Vazquez S, Laurencot C, Misasi J, Arnold F, Carlton K, Lawlor H, Gall J, Bailer RT, McDermott A, Capparelli E, Koup RA, Mascola JR, Graham BS, Sullivan NJ, Ledgerwood JE; VRC 608 Study team. Safety, tolerability, pharmacokinetics, and immunogenicity of the therapeutic monoclonal antibody mAb114 targeting Ebola virus glycoprotein (VRC 608): an open-label phase 1 study. Lancet. 2019 Mar 2;393(10174):889-898. doi: 10.1016/S0140-6736(19)30036-4. Epub 2019 Jan 24. Erratum In: Lancet. 2020 May 30;395(10238):1694." + } + ], + "seeAlsoLinks": [ + { + "label": "NIH Clinical Center Detailed Web Page", + "url": "https://clinicalstudies.info.nih.gov/cgi/detail.cgi?A_2018-I-0069.html" + }, + { + "label": "NIH Press Release", + "url": "https://www.nih.gov/news-events/news-releases/investigational-monoclonal-antibody-treat-ebola-safe-adults" + } + ] + }, + "ipdSharingStatementModule": { + "ipdSharing": "NO" + } + }, + "resultsSection": { + "participantFlowModule": { + "recruitmentDetails": "Healthy adults were recruited at the NIH Clinical Center in Bethesda, Maryland", + "groups": [ + { + "id": "FG000", + "title": "Group 1: 5 mg/kg IV", + "description": "Group 1 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 5 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "FG001", + "title": "Group 2: 25 mg/kg IV", + "description": "Group 2 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 25 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "FG002", + "title": "Group 3: 50 mg/kg IV", + "description": "Group 3 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 50 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + } + ], + "periods": [ + { + "title": "Overall Study", + "milestones": [ + { + "type": "STARTED", + "achievements": [ + { + "groupId": "FG000", + "numSubjects": "3" + }, + { + "groupId": "FG001", + "numSubjects": "5" + }, + { + "groupId": "FG002", + "numSubjects": "11" + } + ] + }, + { + "type": "Received MAb114", + "achievements": [ + { + "groupId": "FG000", + "numSubjects": "3" + }, + { + "groupId": "FG001", + "numSubjects": "5" + }, + { + "groupId": "FG002", + "comment": "One Group 3 subject discontinued prior to MAb114 infusion due to inadequate venous access", + "numSubjects": "10" + } + ] + }, + { + "type": "COMPLETED", + "achievements": [ + { + "groupId": "FG000", + "numSubjects": "2" + }, + { + "groupId": "FG001", + "numSubjects": "5" + }, + { + "groupId": "FG002", + "numSubjects": "8" + } + ] + }, + { + "type": "NOT COMPLETED", + "achievements": [ + { + "groupId": "FG000", + "numSubjects": "1" + }, + { + "groupId": "FG001", + "numSubjects": "0" + }, + { + "groupId": "FG002", + "numSubjects": "3" + } + ] + } + ], + "dropWithdraws": [ + { + "type": "Enrolled, but product never administered", + "reasons": [ + { + "groupId": "FG000", + "numSubjects": "0" + }, + { + "groupId": "FG001", + "numSubjects": "0" + }, + { + "groupId": "FG002", + "numSubjects": "1" + } + ] + }, + { + "type": "Moved from area", + "reasons": [ + { + "groupId": "FG000", + "numSubjects": "0" + }, + { + "groupId": "FG001", + "numSubjects": "0" + }, + { + "groupId": "FG002", + "numSubjects": "1" + } + ] + }, + { + "type": "Lost to Follow-up", + "reasons": [ + { + "groupId": "FG000", + "numSubjects": "1" + }, + { + "groupId": "FG001", + "numSubjects": "0" + }, + { + "groupId": "FG002", + "numSubjects": "1" + } + ] + } + ] + } + ] + }, + "baselineCharacteristicsModule": { + "populationDescription": "All enrolled subjects", + "groups": [ + { + "id": "BG000", + "title": "Group 1: 5 mg/kg IV", + "description": "Group 1 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 5 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "BG001", + "title": "Group 2: 25 mg/kg IV", + "description": "Group 2 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 25 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "BG002", + "title": "Group 3: 50 mg/kg IV", + "description": "Group 3 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 50 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "BG003", + "title": "Total", + "description": "Total of all reporting groups" + } + ], + "denoms": [ + { + "units": "Participants", + "counts": [ + { + "groupId": "BG000", + "value": "3" + }, + { + "groupId": "BG001", + "value": "5" + }, + { + "groupId": "BG002", + "value": "11" + }, + { + "groupId": "BG003", + "value": "19" + } + ] + } + ], + "measures": [ + { + "title": "Age, Continuous", + "paramType": "MEAN", + "dispersionType": "STANDARD_DEVIATION", + "unitOfMeasure": "years", + "classes": [ + { + "categories": [ + { + "measurements": [ + { + "groupId": "BG000", + "value": "39.7", + "spread": "7.4" + }, + { + "groupId": "BG001", + "value": "36.4", + "spread": "9.8" + }, + { + "groupId": "BG002", + "value": "39.0", + "spread": "13.9" + }, + { + "groupId": "BG003", + "value": "38.4", + "spread": "11.7" + } + ] + } + ] + } + ] + }, + { + "title": "Age, Customized", + "paramType": "COUNT_OF_PARTICIPANTS", + "unitOfMeasure": "Participants", + "classes": [ + { + "title": "21-30 years", + "categories": [ + { + "measurements": [ + { + "groupId": "BG000", + "value": "0" + }, + { + "groupId": "BG001", + "value": "1" + }, + { + "groupId": "BG002", + "value": "4" + }, + { + "groupId": "BG003", + "value": "5" + } + ] + } + ] + }, + { + "title": "31-40 years", + "categories": [ + { + "measurements": [ + { + "groupId": "BG000", + "value": "2" + }, + { + "groupId": "BG001", + "value": "2" + }, + { + "groupId": "BG002", + "value": "2" + }, + { + "groupId": "BG003", + "value": "6" + } + ] + } + ] + }, + { + "title": "41-50 years", + "categories": [ + { + "measurements": [ + { + "groupId": "BG000", + "value": "1" + }, + { + "groupId": "BG001", + "value": "2" + }, + { + "groupId": "BG002", + "value": "1" + }, + { + "groupId": "BG003", + "value": "4" + } + ] + } + ] + }, + { + "title": "51-60 years", + "categories": [ + { + "measurements": [ + { + "groupId": "BG000", + "value": "0" + }, + { + "groupId": "BG001", + "value": "0" + }, + { + "groupId": "BG002", + "value": "4" + }, + { + "groupId": "BG003", + "value": "4" + } + ] + } + ] + } + ] + }, + { + "title": "Sex: Female, Male", + "paramType": "COUNT_OF_PARTICIPANTS", + "unitOfMeasure": "Participants", + "classes": [ + { + "categories": [ + { + "title": "Female", + "measurements": [ + { + "groupId": "BG000", + "value": "1" + }, + { + "groupId": "BG001", + "value": "4" + }, + { + "groupId": "BG002", + "value": "7" + }, + { + "groupId": "BG003", + "value": "12" + } + ] + }, + { + "title": "Male", + "measurements": [ + { + "groupId": "BG000", + "value": "2" + }, + { + "groupId": "BG001", + "value": "1" + }, + { + "groupId": "BG002", + "value": "4" + }, + { + "groupId": "BG003", + "value": "7" + } + ] + } + ] + } + ] + }, + { + "title": "Ethnicity (NIH/OMB)", + "paramType": "COUNT_OF_PARTICIPANTS", + "unitOfMeasure": "Participants", + "classes": [ + { + "categories": [ + { + "title": "Hispanic or Latino", + "measurements": [ + { + "groupId": "BG000", + "value": "0" + }, + { + "groupId": "BG001", + "value": "1" + }, + { + "groupId": "BG002", + "value": "1" + }, + { + "groupId": "BG003", + "value": "2" + } + ] + }, + { + "title": "Not Hispanic or Latino", + "measurements": [ + { + "groupId": "BG000", + "value": "3" + }, + { + "groupId": "BG001", + "value": "4" + }, + { + "groupId": "BG002", + "value": "10" + }, + { + "groupId": "BG003", + "value": "17" + } + ] + }, + { + "title": "Unknown or Not Reported", + "measurements": [ + { + "groupId": "BG000", + "value": "0" + }, + { + "groupId": "BG001", + "value": "0" + }, + { + "groupId": "BG002", + "value": "0" + }, + { + "groupId": "BG003", + "value": "0" + } + ] + } + ] + } + ] + }, + { + "title": "Race (NIH/OMB)", + "paramType": "COUNT_OF_PARTICIPANTS", + "unitOfMeasure": "Participants", + "classes": [ + { + "categories": [ + { + "title": "American Indian or Alaska Native", + "measurements": [ + { + "groupId": "BG000", + "value": "0" + }, + { + "groupId": "BG001", + "value": "0" + }, + { + "groupId": "BG002", + "value": "0" + }, + { + "groupId": "BG003", + "value": "0" + } + ] + }, + { + "title": "Asian", + "measurements": [ + { + "groupId": "BG000", + "value": "0" + }, + { + "groupId": "BG001", + "value": "0" + }, + { + "groupId": "BG002", + "value": "1" + }, + { + "groupId": "BG003", + "value": "1" + } + ] + }, + { + "title": "Native Hawaiian or Other Pacific Islander", + "measurements": [ + { + "groupId": "BG000", + "value": "0" + }, + { + "groupId": "BG001", + "value": "0" + }, + { + "groupId": "BG002", + "value": "0" + }, + { + "groupId": "BG003", + "value": "0" + } + ] + }, + { + "title": "Black or African American", + "measurements": [ + { + "groupId": "BG000", + "value": "0" + }, + { + "groupId": "BG001", + "value": "0" + }, + { + "groupId": "BG002", + "value": "1" + }, + { + "groupId": "BG003", + "value": "1" + } + ] + }, + { + "title": "White", + "measurements": [ + { + "groupId": "BG000", + "value": "2" + }, + { + "groupId": "BG001", + "value": "5" + }, + { + "groupId": "BG002", + "value": "7" + }, + { + "groupId": "BG003", + "value": "14" + } + ] + }, + { + "title": "More than one race", + "measurements": [ + { + "groupId": "BG000", + "value": "1" + }, + { + "groupId": "BG001", + "value": "0" + }, + { + "groupId": "BG002", + "value": "2" + }, + { + "groupId": "BG003", + "value": "3" + } + ] + }, + { + "title": "Unknown or Not Reported", + "measurements": [ + { + "groupId": "BG000", + "value": "0" + }, + { + "groupId": "BG001", + "value": "0" + }, + { + "groupId": "BG002", + "value": "0" + }, + { + "groupId": "BG003", + "value": "0" + } + ] + } + ] + } + ] + }, + { + "title": "Region of Enrollment", + "paramType": "COUNT_OF_PARTICIPANTS", + "unitOfMeasure": "Participants", + "classes": [ + { + "title": "United States", + "categories": [ + { + "measurements": [ + { + "groupId": "BG000", + "value": "3" + }, + { + "groupId": "BG001", + "value": "5" + }, + { + "groupId": "BG002", + "value": "11" + }, + { + "groupId": "BG003", + "value": "19" + } + ] + } + ] + } + ] + }, + { + "title": "Weight", + "paramType": "MEAN", + "dispersionType": "STANDARD_DEVIATION", + "unitOfMeasure": "kg", + "classes": [ + { + "categories": [ + { + "measurements": [ + { + "groupId": "BG000", + "value": "88", + "spread": "8.8" + }, + { + "groupId": "BG001", + "value": "66.3", + "spread": "12.6" + }, + { + "groupId": "BG002", + "value": "73.5", + "spread": "12.4" + }, + { + "groupId": "BG003", + "value": "73.9", + "spread": "13.4" + } + ] + } + ] + } + ] + }, + { + "title": "Education", + "paramType": "COUNT_OF_PARTICIPANTS", + "unitOfMeasure": "Participants", + "classes": [ + { + "categories": [ + { + "title": "College or University", + "measurements": [ + { + "groupId": "BG000", + "value": "2" + }, + { + "groupId": "BG001", + "value": "3" + }, + { + "groupId": "BG002", + "value": "8" + }, + { + "groupId": "BG003", + "value": "13" + } + ] + }, + { + "title": "Advanced Degree", + "measurements": [ + { + "groupId": "BG000", + "value": "1" + }, + { + "groupId": "BG001", + "value": "2" + }, + { + "groupId": "BG002", + "value": "3" + }, + { + "groupId": "BG003", + "value": "6" + } + ] + } + ] + } + ] + } + ] + }, + "outcomeMeasuresModule": { + "outcomeMeasures": [ + { + "type": "PRIMARY", + "title": "Number of Subjects Experiencing Infusion Reaction During Product Administration", + "description": "Possible infusion reaction symptoms: unusually tired/feeling unwell, muscles aches, headache, chills, rigors, nausea, fever, joint pain, urticaria/rash, and pruritus. Also information was collected if administration was slowed down or stopped for reactogenicity reasons.", + "populationDescription": "Subjects who received MAb114 (N=18), where \"N\" signifies number of subjects analyzed for this outcome measure.", + "reportingStatus": "POSTED", + "paramType": "COUNT_OF_PARTICIPANTS", + "unitOfMeasure": "Participants", + "timeFrame": "About 30 minutes of product administration", + "groups": [ + { + "id": "OG000", + "title": "Group 1: 5 mg/kg IV (n=3)", + "description": "Group 1 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 5 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG001", + "title": "Group 2: 25 mg/kg IV (n=5)", + "description": "Group 2 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 25 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG002", + "title": "Group 3: 50 mg/kg IV (n=10)", + "description": "Group 3 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 50 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + } + ], + "denoms": [ + { + "units": "Participants", + "counts": [ + { + "groupId": "OG000", + "value": "3" + }, + { + "groupId": "OG001", + "value": "5" + }, + { + "groupId": "OG002", + "value": "10" + } + ] + } + ], + "classes": [ + { + "title": "Number who experienced infusion reaction", + "categories": [ + { + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + } + ] + } + ] + }, + { + "title": "Number who completed infusion in about 30 min", + "categories": [ + { + "measurements": [ + { + "groupId": "OG000", + "value": "3" + }, + { + "groupId": "OG001", + "value": "5" + }, + { + "groupId": "OG002", + "value": "10" + } + ] + } + ] + } + ] + }, + { + "type": "PRIMARY", + "title": "Number of Subjects Reporting Systemic Reactogenicity Signs and Symptoms Within 3 Days of Product Administration", + "description": "Subjects recorded 3-day systemic reactogenicity symptoms in a diary after study product administration. Solicited systemic symptoms include: unusually tired/feeling unwell, muscles aches, headache, chills, nausea, fever and joint pain. Subjects recorded highest measured temperature daily. Clinicians reviewed the diary with the subject and collected resolution information for any symptoms that were not resolved within 3 days. Subjects were counted once for each symptom at the worst severity if they indicated experiencing the symptom at any severity during the reporting period. The number reported for \"Any Systemic Symptom\" is the number of subjects experiencing any systemic symptom as reported at the worst severity. Solicited reactogenicity was recorded without an attribution assessment. Grading was done by Division of AIDS Table for Grading the Severity of Adult and Pediatric Adverse Events Version 2.1.", + "populationDescription": "Subjects who received MAb114 (N=18), where \"N\" signifies number of subjects analyzed for this outcome measure.", + "reportingStatus": "POSTED", + "paramType": "COUNT_OF_PARTICIPANTS", + "unitOfMeasure": "Participants", + "timeFrame": "3 days after product administration", + "groups": [ + { + "id": "OG000", + "title": "Group 1: 5 mg/kg IV (n=3)", + "description": "Group 1 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 5 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG001", + "title": "Group 2: 25 mg/kg IV (n=5)", + "description": "Group 2 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 25 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG002", + "title": "Group 3: 50 mg/kg IV (n=10)", + "description": "Group 3 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 50 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG003", + "title": "Overall (n=18)", + "description": "Total number of subjects who received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0" + } + ], + "denoms": [ + { + "units": "Participants", + "counts": [ + { + "groupId": "OG000", + "value": "3" + }, + { + "groupId": "OG001", + "value": "5" + }, + { + "groupId": "OG002", + "value": "10" + }, + { + "groupId": "OG003", + "value": "18" + } + ] + } + ], + "classes": [ + { + "title": "Malaise", + "categories": [ + { + "title": "None", + "measurements": [ + { + "groupId": "OG000", + "value": "3" + }, + { + "groupId": "OG001", + "value": "4" + }, + { + "groupId": "OG002", + "value": "8" + }, + { + "groupId": "OG003", + "value": "15" + } + ] + }, + { + "title": "Mild", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "1" + }, + { + "groupId": "OG002", + "value": "2" + }, + { + "groupId": "OG003", + "value": "3" + } + ] + }, + { + "title": "Moderate", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + }, + { + "title": "Severe", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + } + ] + }, + { + "title": "Myalgia", + "categories": [ + { + "title": "None", + "measurements": [ + { + "groupId": "OG000", + "value": "3" + }, + { + "groupId": "OG001", + "value": "4" + }, + { + "groupId": "OG002", + "value": "9" + }, + { + "groupId": "OG003", + "value": "16" + } + ] + }, + { + "title": "Mild", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "1" + }, + { + "groupId": "OG002", + "value": "1" + }, + { + "groupId": "OG003", + "value": "2" + } + ] + }, + { + "title": "Moderate", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + }, + { + "title": "Severe", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + } + ] + }, + { + "title": "Headache", + "categories": [ + { + "title": "None", + "measurements": [ + { + "groupId": "OG000", + "value": "3" + }, + { + "groupId": "OG001", + "value": "3" + }, + { + "groupId": "OG002", + "value": "8" + }, + { + "groupId": "OG003", + "value": "14" + } + ] + }, + { + "title": "Mild", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "2" + }, + { + "groupId": "OG002", + "value": "2" + }, + { + "groupId": "OG003", + "value": "4" + } + ] + }, + { + "title": "Moderate", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + }, + { + "title": "Severe", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + } + ] + }, + { + "title": "Chills", + "categories": [ + { + "title": "None", + "measurements": [ + { + "groupId": "OG000", + "value": "3" + }, + { + "groupId": "OG001", + "value": "4" + }, + { + "groupId": "OG002", + "value": "9" + }, + { + "groupId": "OG003", + "value": "16" + } + ] + }, + { + "title": "Mild", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "1" + }, + { + "groupId": "OG002", + "value": "1" + }, + { + "groupId": "OG003", + "value": "2" + } + ] + }, + { + "title": "Moderate", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + }, + { + "title": "Severe", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + } + ] + }, + { + "title": "Nausea", + "categories": [ + { + "title": "None", + "measurements": [ + { + "groupId": "OG000", + "value": "3" + }, + { + "groupId": "OG001", + "value": "4" + }, + { + "groupId": "OG002", + "value": "9" + }, + { + "groupId": "OG003", + "value": "16" + } + ] + }, + { + "title": "Mild", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "1" + }, + { + "groupId": "OG002", + "value": "1" + }, + { + "groupId": "OG003", + "value": "2" + } + ] + }, + { + "title": "Moderate", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + }, + { + "title": "Severe", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + } + ] + }, + { + "title": "Fever", + "categories": [ + { + "title": "None", + "measurements": [ + { + "groupId": "OG000", + "value": "3" + }, + { + "groupId": "OG001", + "value": "5" + }, + { + "groupId": "OG002", + "value": "10" + }, + { + "groupId": "OG003", + "value": "18" + } + ] + }, + { + "title": "Mild", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + }, + { + "title": "Moderate", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + }, + { + "title": "Severe", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + } + ] + }, + { + "title": "Joint Pain", + "categories": [ + { + "title": "None", + "measurements": [ + { + "groupId": "OG000", + "value": "3" + }, + { + "groupId": "OG001", + "value": "4" + }, + { + "groupId": "OG002", + "value": "9" + }, + { + "groupId": "OG003", + "value": "16" + } + ] + }, + { + "title": "Mild", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "1" + }, + { + "groupId": "OG002", + "value": "1" + }, + { + "groupId": "OG003", + "value": "2" + } + ] + }, + { + "title": "Moderate", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + }, + { + "title": "Severe", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + } + ] + }, + { + "title": "Any systemic symptom reported", + "categories": [ + { + "title": "None", + "measurements": [ + { + "groupId": "OG000", + "value": "3" + }, + { + "groupId": "OG001", + "value": "3" + }, + { + "groupId": "OG002", + "value": "8" + }, + { + "groupId": "OG003", + "value": "14" + } + ] + }, + { + "title": "Mild", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "2" + }, + { + "groupId": "OG002", + "value": "2" + }, + { + "groupId": "OG003", + "value": "4" + } + ] + }, + { + "title": "Moderate", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + }, + { + "title": "Severe", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + } + ] + } + ] + }, + { + "type": "PRIMARY", + "title": "Number of Subjects Reporting Local Reactogenicity Signs and Symptoms Within 7 Days of Product Administration", + "description": "Local reactogenicity symptoms were assessed and recorded by clinicians. Solicited local symptoms include pain/tenderness, swelling, redness, bruising, and pruritus (itchiness) at the product administration site. Clinicians assessed the study product administration site for local symptoms on the day of product administration after completion of the administration and on Days 1, 2 and 7 post administration. Subjects were counted once for each symptom at the worst severity if they experienced the symptom at any severity during the reporting period. If symptoms were experienced, clinicians collected resolution information for any symptom that wasn't resolved within 7 days. The number reported for \"Any Local Symptom\" is the number of subjects reporting any local symptom as reported at the worst severity. Solicited reactogenicity was recorded without attribution assessment. Grading was done by Division of AIDS Table for Grading the Severity of Adult and Pediatric Adverse Events Version 2.1.", + "populationDescription": "Subjects who received MAb114 (N=18), where \"N\" signifies number of subjects analyzed for this outcome measure.", + "reportingStatus": "POSTED", + "paramType": "COUNT_OF_PARTICIPANTS", + "unitOfMeasure": "Participants", + "timeFrame": "7 days after product administration", + "groups": [ + { + "id": "OG000", + "title": "Group 1: 5 mg/kg IV (n=3)", + "description": "Group 1 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 5 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG001", + "title": "Group 2: 25 mg/kg IV (n=5)", + "description": "Group 2 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 25 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG002", + "title": "Group 3: 50 mg/kg IV (n=10)", + "description": "Group 3 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 50 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG003", + "title": "Overall (n=18)", + "description": "Total number of subjects who received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0" + } + ], + "denoms": [ + { + "units": "Participants", + "counts": [ + { + "groupId": "OG000", + "value": "3" + }, + { + "groupId": "OG001", + "value": "5" + }, + { + "groupId": "OG002", + "value": "10" + }, + { + "groupId": "OG003", + "value": "18" + } + ] + } + ], + "classes": [ + { + "title": "Pain/Tenderness", + "categories": [ + { + "title": "None", + "measurements": [ + { + "groupId": "OG000", + "value": "3" + }, + { + "groupId": "OG001", + "value": "5" + }, + { + "groupId": "OG002", + "value": "10" + }, + { + "groupId": "OG003", + "value": "18" + } + ] + }, + { + "title": "Mild", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + }, + { + "title": "Moderate", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + }, + { + "title": "Severe", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + } + ] + }, + { + "title": "Bruising", + "categories": [ + { + "title": "None", + "measurements": [ + { + "groupId": "OG000", + "value": "3" + }, + { + "groupId": "OG001", + "value": "5" + }, + { + "groupId": "OG002", + "value": "10" + }, + { + "groupId": "OG003", + "value": "18" + } + ] + }, + { + "title": "Mild", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + }, + { + "title": "Moderate", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + }, + { + "title": "Severe", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + } + ] + }, + { + "title": "Swelling", + "categories": [ + { + "title": "None", + "measurements": [ + { + "groupId": "OG000", + "value": "3" + }, + { + "groupId": "OG001", + "value": "5" + }, + { + "groupId": "OG002", + "value": "10" + }, + { + "groupId": "OG003", + "value": "18" + } + ] + }, + { + "title": "Mild", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + }, + { + "title": "Moderate", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + }, + { + "title": "Severe", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + } + ] + }, + { + "title": "Redness", + "categories": [ + { + "title": "None", + "measurements": [ + { + "groupId": "OG000", + "value": "3" + }, + { + "groupId": "OG001", + "value": "5" + }, + { + "groupId": "OG002", + "value": "10" + }, + { + "groupId": "OG003", + "value": "18" + } + ] + }, + { + "title": "Mild", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + }, + { + "title": "Moderate", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + }, + { + "title": "Severe", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + } + ] + }, + { + "title": "Pruritis", + "categories": [ + { + "title": "None", + "measurements": [ + { + "groupId": "OG000", + "value": "3" + }, + { + "groupId": "OG001", + "value": "5" + }, + { + "groupId": "OG002", + "value": "10" + }, + { + "groupId": "OG003", + "value": "18" + } + ] + }, + { + "title": "Mild", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + }, + { + "title": "Moderate", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + }, + { + "title": "Severe", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + } + ] + }, + { + "title": "Any Local Symptoms Reported", + "categories": [ + { + "title": "None", + "measurements": [ + { + "groupId": "OG000", + "value": "3" + }, + { + "groupId": "OG001", + "value": "5" + }, + { + "groupId": "OG002", + "value": "10" + }, + { + "groupId": "OG003", + "value": "18" + } + ] + }, + { + "title": "Mild", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + }, + { + "title": "Moderate", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + }, + { + "title": "Severe", + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + } + ] + } + ] + }, + { + "type": "PRIMARY", + "title": "Number of Subjects Reporting 1 or More Unsolicited Non-Serious Adverse Events", + "description": "Unsolicited adverse events (AEs) collected during the period from study product administration at Day 0 through 28 days after product administration. After the indicated time period through the last expected study visit at 24 weeks after product administration, only new chronic medical conditions collected as unsolicited AEs.\n\nThe number reported is the number of subjects who experienced at least one AE in the reporting period. A subject with multiple experiences of the same event is counted once using the event of worst severity. The relationship between an AE and the product was assessed by the investigator on the basis of his or her clinical judgment and the protocol-specific definitions of Related - There is a reasonable possibility that the AE may be related to the study product and Not Related - There is not a reasonable possibility that the AE is related to the study product.", + "populationDescription": "Subjects who received MAb114 (N=18), where \"N\" signifies number of subjects analyzed for this outcome measure.", + "reportingStatus": "POSTED", + "paramType": "COUNT_OF_PARTICIPANTS", + "unitOfMeasure": "Participants", + "timeFrame": "Through 24 weeks after product administration", + "groups": [ + { + "id": "OG000", + "title": "Group 1: 5 mg/kg IV (n=3)", + "description": "Group 1 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 5 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG001", + "title": "Group 2: 25 mg/kg IV (n=5)", + "description": "Group 2 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 25 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG002", + "title": "Group 3: 50 mg/kg IV (n=10)", + "description": "Group 3 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 50 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG003", + "title": "Overall (n=18)", + "description": "Total number of subjects who received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0" + } + ], + "denoms": [ + { + "units": "Participants", + "counts": [ + { + "groupId": "OG000", + "value": "3" + }, + { + "groupId": "OG001", + "value": "5" + }, + { + "groupId": "OG002", + "value": "10" + }, + { + "groupId": "OG003", + "value": "18" + } + ] + } + ], + "classes": [ + { + "title": "Related to MAb114 Administration", + "categories": [ + { + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + } + ] + }, + { + "title": "Not Related to MAb114 Administration", + "categories": [ + { + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "2" + }, + { + "groupId": "OG002", + "value": "5" + }, + { + "groupId": "OG003", + "value": "7" + } + ] + } + ] + } + ] + }, + { + "type": "PRIMARY", + "title": "Number of Subjects Reporting Serious Adverse Events", + "description": "Serious adverse events (SAEs) collected during the period from study product administration at Day 0 through 24 weeks after product administration. Grading done by Division of AIDS Table for Grading the Severity of Adult and Pediatric Adverse Events Version 2.1. The relationship between a SAE and the product was assessed by the investigator on the basis of his or her clinical judgment and the protocol-specific definitions of Related - There is a reasonable possibility that the SAE may be related to the study product and Not Related - There is not a reasonable possibility that the SAE is related to the study product.", + "populationDescription": "Subjects who received MAb114 (N=18), where \"N\" signifies number of subjects analyzed for this outcome measure.", + "reportingStatus": "POSTED", + "paramType": "COUNT_OF_PARTICIPANTS", + "unitOfMeasure": "Participants", + "timeFrame": "Through 24 weeks after product administration", + "groups": [ + { + "id": "OG000", + "title": "Group 1: 5 mg/kg IV (n=3)", + "description": "Group 1 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 5 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG001", + "title": "Group 2: 25 mg/kg IV (n=5)", + "description": "Group 2 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 25 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG002", + "title": "Group 3: 50 mg/kg IV (n=10)", + "description": "Group 3 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 50 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG003", + "title": "Overall (n=18)", + "description": "Total number of subjects who received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0" + } + ], + "denoms": [ + { + "units": "Participants", + "counts": [ + { + "groupId": "OG000", + "value": "3" + }, + { + "groupId": "OG001", + "value": "5" + }, + { + "groupId": "OG002", + "value": "10" + }, + { + "groupId": "OG003", + "value": "18" + } + ] + } + ], + "classes": [ + { + "title": "Related to MAb114 Administration", + "categories": [ + { + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + }, + { + "groupId": "OG003", + "value": "0" + } + ] + } + ] + }, + { + "title": "Not Related to MAb114 Administration", + "categories": [ + { + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "1" + }, + { + "groupId": "OG003", + "value": "1" + } + ] + } + ] + } + ] + }, + { + "type": "SECONDARY", + "title": "Maximum Observed Serum Concentration (Cmax) of MAb114", + "description": "Cmax is the peak serum concentration that MAb114 achieves after it has been administered; it is determined as a maximum value on the summary pharmacokinetic (PK) curve for each study group.", + "populationDescription": "Subjects with 28 days of pharmacokinetic data.", + "reportingStatus": "POSTED", + "paramType": "MEAN", + "dispersionType": "Standard Deviation", + "unitOfMeasure": "µg/mL", + "timeFrame": "Pre-infusion (baseline), end of infusion (0h), 1 hr, 3 hr, 6 hr, 24 hr, 48 hr and 7-28 days post-infusion", + "groups": [ + { + "id": "OG000", + "title": "Group 1: 5 mg/kg IV (n=3)", + "description": "Group 1 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 5 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG001", + "title": "Group 2: 25 mg/kg IV (n=5)", + "description": "Group 2 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 25 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG002", + "title": "Group 3: 50 mg/kg IV (n=5)", + "description": "Group 3 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 50 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + } + ], + "denoms": [ + { + "units": "Participants", + "counts": [ + { + "groupId": "OG000", + "value": "3" + }, + { + "groupId": "OG001", + "value": "5" + }, + { + "groupId": "OG002", + "value": "5" + } + ] + } + ], + "classes": [ + { + "categories": [ + { + "measurements": [ + { + "groupId": "OG000", + "value": "198.45", + "spread": "45.15" + }, + { + "groupId": "OG001", + "value": "829.38", + "spread": "237.40" + }, + { + "groupId": "OG002", + "value": "1961.21", + "spread": "339.83" + } + ] + } + ] + } + ] + }, + { + "type": "SECONDARY", + "title": "Time to Reach Maximum Observed Serum Concentration (Tmax) of MAb114", + "description": "Tmax is the time it takes to reach Cmax of MAb114 after it has been administered; it is determined based on the summary PK curve for each study group.", + "populationDescription": "Subjects with 28 days of pharmacokinetic data.", + "reportingStatus": "POSTED", + "paramType": "MEAN", + "dispersionType": "Standard Deviation", + "unitOfMeasure": "hours", + "timeFrame": "Pre-infusion (baseline), end of infusion (0h), 1 hr, 3 hr, 6 hr, 24 hr, 48 hr and 7-28 days post-infusion", + "groups": [ + { + "id": "OG000", + "title": "Group 1: 5 mg/kg IV (n=3)", + "description": "Group 1 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 5 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG001", + "title": "Group 2: 25 mg/kg IV (n=5)", + "description": "Group 2 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 25 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG002", + "title": "Group 3: 50 mg/kg IV (n=5)", + "description": "Group 3 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 50 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + } + ], + "denoms": [ + { + "units": "Participants", + "counts": [ + { + "groupId": "OG000", + "value": "3" + }, + { + "groupId": "OG001", + "value": "5" + }, + { + "groupId": "OG002", + "value": "5" + } + ] + } + ], + "classes": [ + { + "categories": [ + { + "measurements": [ + { + "groupId": "OG000", + "value": "3.21", + "spread": "1.56" + }, + { + "groupId": "OG001", + "value": "2.99", + "spread": "2.16" + }, + { + "groupId": "OG002", + "value": "2.75", + "spread": "1.63" + } + ] + } + ] + } + ] + }, + { + "type": "SECONDARY", + "title": "Mean Serum Concentration of MAb114", + "description": "The mean of individual subject MAb114 serum concentrations by administered dose group", + "populationDescription": "Subjects with 28 days of pharmacokinetic data.", + "reportingStatus": "POSTED", + "paramType": "MEAN", + "dispersionType": "Standard Deviation", + "unitOfMeasure": "µg/mL", + "timeFrame": "Pre-infusion (baseline), end of infusion (0h), 1 hr, 3 hr, 6 hr, 24 hr, 48 hr and 7-28 days post-infusion", + "groups": [ + { + "id": "OG000", + "title": "Group 1: 5 mg/kg IV (n=3)", + "description": "Group 1 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 5 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG001", + "title": "Group 2: 25 mg/kg IV (n=5)", + "description": "Group 2 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 25 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG002", + "title": "Group 3: 50 mg/kg IV (n=5)", + "description": "Group 3 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 50 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + } + ], + "denoms": [ + { + "units": "Participants", + "counts": [ + { + "groupId": "OG000", + "value": "3" + }, + { + "groupId": "OG001", + "value": "5" + }, + { + "groupId": "OG002", + "value": "5" + } + ] + } + ], + "classes": [ + { + "title": "Concentration at Day 7", + "categories": [ + { + "measurements": [ + { + "groupId": "OG000", + "value": "52.60", + "spread": "22.19" + }, + { + "groupId": "OG001", + "value": "373.12", + "spread": "75.32" + }, + { + "groupId": "OG002", + "value": "692.64", + "spread": "201.63" + } + ] + } + ] + }, + { + "title": "Concentration at Day 14", + "categories": [ + { + "measurements": [ + { + "groupId": "OG000", + "value": "42.90", + "spread": "5.53" + }, + { + "groupId": "OG001", + "value": "272.91", + "spread": "46.16" + }, + { + "groupId": "OG002", + "value": "629.34", + "spread": "118.19" + } + ] + } + ] + }, + { + "title": "Concentration at Day 28", + "categories": [ + { + "measurements": [ + { + "groupId": "OG000", + "value": "25.46", + "spread": "5.96" + }, + { + "groupId": "OG001", + "value": "180.19", + "spread": "38.82" + }, + { + "groupId": "OG002", + "value": "427.21", + "spread": "88.43" + } + ] + } + ] + }, + { + "title": "Average Concentration Days 0-28", + "categories": [ + { + "measurements": [ + { + "groupId": "OG000", + "value": "52.87", + "spread": "10.87" + }, + { + "groupId": "OG001", + "value": "306.65", + "spread": "32.14" + }, + { + "groupId": "OG002", + "value": "663.87", + "spread": "129.55" + } + ] + } + ] + } + ] + }, + { + "type": "SECONDARY", + "title": "Overall IV Half-life (T1/2) of MAb114", + "description": "Half-life (T1/2) is the time required for half of the drug to be eliminated from the serum.", + "populationDescription": "Subjects with 56 days of pharmacokinetic data. No standard deviation reported for a Group 3 single subject data.", + "reportingStatus": "POSTED", + "paramType": "MEAN", + "dispersionType": "Standard Deviation", + "unitOfMeasure": "days", + "timeFrame": "Pre-infusion (baseline), end of infusion (0h), 1 hr, 3 hr, 6 hr, 24 hr, 48 hr and 7-56 days post-infusion", + "groups": [ + { + "id": "OG000", + "title": "Group 1: 5 mg/kg IV (n=3)", + "description": "Group 1 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 5 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG001", + "title": "Group 2: 25 mg/kg IV (n=5)", + "description": "Group 2 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 25 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG002", + "title": "Group 3: 50 mg/kg IV (n=1)", + "description": "Group 3 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 50 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG003", + "title": "Overall (n=9)", + "description": "Total number of subjects who received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0" + } + ], + "denoms": [ + { + "units": "Participants", + "counts": [ + { + "groupId": "OG000", + "value": "3" + }, + { + "groupId": "OG001", + "value": "5" + }, + { + "groupId": "OG002", + "value": "1" + }, + { + "groupId": "OG003", + "value": "9" + } + ] + } + ], + "classes": [ + { + "categories": [ + { + "measurements": [ + { + "groupId": "OG000", + "value": "20.1", + "spread": "6.9" + }, + { + "groupId": "OG001", + "value": "26.7", + "spread": "3.8" + }, + { + "groupId": "OG002", + "value": "23.6" + }, + { + "groupId": "OG003", + "value": "24.2", + "spread": "1.8" + } + ] + } + ] + } + ] + }, + { + "type": "SECONDARY", + "title": "Area Under the Curve (AUC0-28D)", + "description": "The AUC0-28D represents the total drug exposure in 28 days after MAb114 administration; it is determined based on the summary PK curve for each group.", + "populationDescription": "Subjects with 28 days of pharmacokinetic data.", + "reportingStatus": "POSTED", + "paramType": "MEAN", + "dispersionType": "Standard Deviation", + "unitOfMeasure": "µg x day/mL", + "timeFrame": "Pre-infusion (baseline), end of infusion (0h), 1 hr, 3 hr, 6 hr, 24 hr, 48 hr and 7-28 days post-infusion", + "groups": [ + { + "id": "OG000", + "title": "Group 1: 5 mg/kg IV (n=3)", + "description": "Group 1 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 5 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG001", + "title": "Group 2: 25 mg/kg IV (n=5)", + "description": "Group 2 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 25 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG002", + "title": "Group 3: 50 mg/kg IV (n=5)", + "description": "Group 3 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 50 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + } + ], + "denoms": [ + { + "units": "Participants", + "counts": [ + { + "groupId": "OG000", + "value": "3" + }, + { + "groupId": "OG001", + "value": "5" + }, + { + "groupId": "OG002", + "value": "5" + } + ] + } + ], + "classes": [ + { + "categories": [ + { + "measurements": [ + { + "groupId": "OG000", + "value": "1480", + "spread": "304" + }, + { + "groupId": "OG001", + "value": "8586", + "spread": "900" + }, + { + "groupId": "OG002", + "value": "18588", + "spread": "3627" + } + ] + } + ] + } + ] + }, + { + "type": "SECONDARY", + "title": "Volume of Distribution (Vd) at Steady-state", + "description": "Theoretical volume that would be necessary to contain the total amount of administered drug at the same concentration as observed in plasma. It represents the degree to which a drug is distributed in body tissue rather than the plasma and calculated based in the PK curve for each study group.", + "populationDescription": "Subjects with 28 days of pharmacokinetic data.", + "reportingStatus": "POSTED", + "paramType": "MEAN", + "dispersionType": "Standard Deviation", + "unitOfMeasure": "Liters", + "timeFrame": "Pre-infusion (baseline), end of infusion (0h), 1 hr, 3 hr, 6 hr, 24 hr, 48 hr and 7-28 days post-infusion", + "groups": [ + { + "id": "OG000", + "title": "Group 1: 5 mg/kg IV (n=3)", + "description": "Group 1 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 5 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG001", + "title": "Group 2: 25 mg/kg IV (n=5)", + "description": "Group 2 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 25 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG002", + "title": "Group 3: 50 mg/kg IV (n=5)", + "description": "Group 3 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 50 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + } + ], + "denoms": [ + { + "units": "Participants", + "counts": [ + { + "groupId": "OG000", + "value": "3" + }, + { + "groupId": "OG001", + "value": "5" + }, + { + "groupId": "OG002", + "value": "5" + } + ] + } + ], + "classes": [ + { + "categories": [ + { + "measurements": [ + { + "groupId": "OG000", + "value": "5.08", + "spread": "0.88" + }, + { + "groupId": "OG001", + "value": "3.93", + "spread": "0.50" + }, + { + "groupId": "OG002", + "value": "4.16", + "spread": "0.74" + } + ] + } + ] + } + ] + }, + { + "type": "SECONDARY", + "title": "MAb114 Clearance Rate", + "description": "Rate of MAb114 elimination divided by the plasma MAb114 concentration; determined based on the summary PK curve for each study group.", + "populationDescription": "Subjects with 28 days of pharmacokinetic data.", + "reportingStatus": "POSTED", + "paramType": "MEAN", + "dispersionType": "Standard Deviation", + "unitOfMeasure": "mL/day", + "timeFrame": "Pre-infusion (baseline), end of infusion (0h), 1 hr, 3 hr, 6 hr, 24 hr, 48 hr and 7-28 days post-infusion", + "groups": [ + { + "id": "OG000", + "title": "Group 1: 5 mg/kg IV (n=3)", + "description": "Group 1 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 5 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG001", + "title": "Group 2: 25 mg/kg IV (n=5)", + "description": "Group 2 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 25 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG002", + "title": "Group 3: 50 mg/kg IV (n=5)", + "description": "Group 3 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 50 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + } + ], + "denoms": [ + { + "units": "Participants", + "counts": [ + { + "groupId": "OG000", + "value": "3" + }, + { + "groupId": "OG001", + "value": "5" + }, + { + "groupId": "OG002", + "value": "5" + } + ] + } + ], + "classes": [ + { + "categories": [ + { + "measurements": [ + { + "groupId": "OG000", + "value": "199", + "spread": "45" + }, + { + "groupId": "OG001", + "value": "108", + "spread": "21" + }, + { + "groupId": "OG002", + "value": "115", + "spread": "15" + } + ] + } + ] + } + ] + }, + { + "type": "SECONDARY", + "title": "Number of Subjects Who Produced Anti-drug Antibodies to MAb114", + "description": "Serum samples collected 28 days and 56 days after MAb114 administration", + "populationDescription": "Subjects with serum samples collected at 28 days and 56 days.", + "reportingStatus": "POSTED", + "paramType": "COUNT_OF_PARTICIPANTS", + "unitOfMeasure": "Participants", + "timeFrame": "Days 28 and 56 post-infusion", + "groups": [ + { + "id": "OG000", + "title": "Group 1: 5 mg/kg IV (n=3)", + "description": "Group 1 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 5 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG001", + "title": "Group 2: 25 mg/kg IV (n=5)", + "description": "Group 2 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 25 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + }, + { + "id": "OG002", + "title": "Group 3: 50 mg/kg IV (n=5)", + "description": "Group 3 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 50 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP)." + } + ], + "denoms": [ + { + "units": "Participants", + "counts": [ + { + "groupId": "OG000", + "value": "3" + }, + { + "groupId": "OG001", + "value": "5" + }, + { + "groupId": "OG002", + "value": "5" + } + ] + } + ], + "classes": [ + { + "title": "Day 28: Subjects with Anti-drug antibodies", + "denoms": [ + { + "units": "Participants", + "counts": [ + { + "groupId": "OG000", + "value": "3" + }, + { + "groupId": "OG001", + "value": "5" + }, + { + "groupId": "OG002", + "value": "5" + } + ] + } + ], + "categories": [ + { + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + } + ] + } + ] + }, + { + "title": "Day 56: Subjects with Anti-drug antibodies", + "denoms": [ + { + "units": "Participants", + "counts": [ + { + "groupId": "OG000", + "value": "3" + }, + { + "groupId": "OG001", + "value": "5" + }, + { + "groupId": "OG002", + "value": "1" + } + ] + } + ], + "categories": [ + { + "measurements": [ + { + "groupId": "OG000", + "value": "0" + }, + { + "groupId": "OG001", + "value": "0" + }, + { + "groupId": "OG002", + "value": "0" + } + ] + } + ] + } + ] + } + ] + }, + "adverseEventsModule": { + "frequencyThreshold": "5", + "timeFrame": "Solicited adverse events (AEs) included systemic AEs reported by subjects at the worst severity through 3 days post product administration; and local administration site AEs reported at the worst severity through 7 days post product administration. Unsolicited AEs were reported from the date of product administration through 28 days thereafter, and new chronic medical conditions and SAEs with onset any time following the date of product administration through 24 weeks.", + "description": "Solicited AEs collected through systematic assessment and unsolicited AEs collected through non-systematic assessment represent the number and percentage of subjects reporting the event. The total number of subjects affected in Other AEs includes subjects reporting at least one solicited and/or unsolicited AE. A subject with multiple experiences of the same event is counted once using the event of worst severity. Solicited events are reported separately from unsolicited events.", + "eventGroups": [ + { + "id": "EG000", + "title": "Group 1: 5 mg/kg IV", + "description": "Group 1 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 5 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP).", + "deathsNumAffected": 0, + "deathsNumAtRisk": 3, + "seriousNumAffected": 0, + "seriousNumAtRisk": 3, + "otherNumAffected": 0, + "otherNumAtRisk": 3 + }, + { + "id": "EG001", + "title": "Group 2: 25 mg/kg IV", + "description": "Group 2 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 25 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP).", + "deathsNumAffected": 0, + "deathsNumAtRisk": 5, + "seriousNumAffected": 0, + "seriousNumAtRisk": 5, + "otherNumAffected": 3, + "otherNumAtRisk": 5 + }, + { + "id": "EG002", + "title": "Group 3: 50 mg/kg IV", + "description": "Group 3 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 50 mg/kg.\n\nVRC-EBOMAB092-00-AB (MAb114): VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP).", + "deathsNumAffected": 0, + "deathsNumAtRisk": 10, + "seriousNumAffected": 1, + "seriousNumAtRisk": 10, + "otherNumAffected": 5, + "otherNumAtRisk": 10 + } + ], + "seriousEvents": [ + { + "term": "Syncope", + "organSystem": "Nervous system disorders", + "sourceVocabulary": "MedDRA (22.0)", + "assessmentType": "NON_SYSTEMATIC_ASSESSMENT", + "stats": [ + { + "groupId": "EG000", + "numAffected": 0, + "numAtRisk": 3 + }, + { + "groupId": "EG001", + "numAffected": 0, + "numAtRisk": 5 + }, + { + "groupId": "EG002", + "numAffected": 1, + "numAtRisk": 10 + } + ] + } + ], + "otherEvents": [ + { + "term": "Hordeolum", + "organSystem": "Infections and infestations", + "sourceVocabulary": "MedDRA (22.0)", + "assessmentType": "NON_SYSTEMATIC_ASSESSMENT", + "stats": [ + { + "groupId": "EG000", + "numAffected": 0, + "numAtRisk": 3 + }, + { + "groupId": "EG001", + "numAffected": 1, + "numAtRisk": 5 + }, + { + "groupId": "EG002", + "numAffected": 0, + "numAtRisk": 10 + } + ] + }, + { + "term": "Dyspepsia", + "organSystem": "Gastrointestinal disorders", + "sourceVocabulary": "MedDRA (22.0)", + "assessmentType": "NON_SYSTEMATIC_ASSESSMENT", + "stats": [ + { + "groupId": "EG000", + "numAffected": 0, + "numAtRisk": 3 + }, + { + "groupId": "EG001", + "numAffected": 1, + "numAtRisk": 5 + }, + { + "groupId": "EG002", + "numAffected": 0, + "numAtRisk": 10 + } + ] + }, + { + "term": "Abdominal pain", + "organSystem": "Gastrointestinal disorders", + "sourceVocabulary": "MedDRA (22.0)", + "assessmentType": "NON_SYSTEMATIC_ASSESSMENT", + "stats": [ + { + "groupId": "EG000", + "numAffected": 0, + "numAtRisk": 3 + }, + { + "groupId": "EG001", + "numAffected": 0, + "numAtRisk": 5 + }, + { + "groupId": "EG002", + "numAffected": 1, + "numAtRisk": 10 + } + ] + }, + { + "term": "Anaemia", + "organSystem": "Blood and lymphatic system disorders", + "sourceVocabulary": "MedDRA (22.0)", + "assessmentType": "NON_SYSTEMATIC_ASSESSMENT", + "stats": [ + { + "groupId": "EG000", + "numAffected": 0, + "numAtRisk": 3 + }, + { + "groupId": "EG001", + "numAffected": 0, + "numAtRisk": 5 + }, + { + "groupId": "EG002", + "numAffected": 1, + "numAtRisk": 10 + } + ] + }, + { + "term": "Alanine aminotransferase increased", + "organSystem": "Investigations", + "sourceVocabulary": "MedDRA (22.0)", + "assessmentType": "NON_SYSTEMATIC_ASSESSMENT", + "stats": [ + { + "groupId": "EG000", + "numAffected": 0, + "numAtRisk": 3 + }, + { + "groupId": "EG001", + "numAffected": 0, + "numAtRisk": 5 + }, + { + "groupId": "EG002", + "numAffected": 1, + "numAtRisk": 10 + } + ] + }, + { + "term": "Hypertension", + "organSystem": "Vascular disorders", + "sourceVocabulary": "MedDRA (22.0)", + "assessmentType": "NON_SYSTEMATIC_ASSESSMENT", + "stats": [ + { + "groupId": "EG000", + "numAffected": 0, + "numAtRisk": 3 + }, + { + "groupId": "EG001", + "numAffected": 0, + "numAtRisk": 5 + }, + { + "groupId": "EG002", + "numAffected": 1, + "numAtRisk": 10 + } + ] + }, + { + "term": "Upper respiratory tract infection", + "organSystem": "Infections and infestations", + "sourceVocabulary": "MedDRA (22.0)", + "assessmentType": "NON_SYSTEMATIC_ASSESSMENT", + "stats": [ + { + "groupId": "EG000", + "numAffected": 0, + "numAtRisk": 3 + }, + { + "groupId": "EG001", + "numAffected": 0, + "numAtRisk": 5 + }, + { + "groupId": "EG002", + "numAffected": 3, + "numAtRisk": 10 + } + ] + }, + { + "term": "Malaise", + "organSystem": "General disorders", + "sourceVocabulary": "MedDRA (22.0)", + "assessmentType": "SYSTEMATIC_ASSESSMENT", + "stats": [ + { + "groupId": "EG000", + "numAffected": 0, + "numAtRisk": 3 + }, + { + "groupId": "EG001", + "numAffected": 1, + "numAtRisk": 5 + }, + { + "groupId": "EG002", + "numAffected": 2, + "numAtRisk": 10 + } + ] + }, + { + "term": "Myalgia", + "organSystem": "Musculoskeletal and connective tissue disorders", + "sourceVocabulary": "MedDRA (22.0)", + "assessmentType": "SYSTEMATIC_ASSESSMENT", + "stats": [ + { + "groupId": "EG000", + "numAffected": 0, + "numAtRisk": 3 + }, + { + "groupId": "EG001", + "numAffected": 1, + "numAtRisk": 5 + }, + { + "groupId": "EG002", + "numAffected": 1, + "numAtRisk": 10 + } + ] + }, + { + "term": "Headache", + "organSystem": "Nervous system disorders", + "sourceVocabulary": "MedDRA (22.0)", + "assessmentType": "SYSTEMATIC_ASSESSMENT", + "stats": [ + { + "groupId": "EG000", + "numAffected": 0, + "numAtRisk": 3 + }, + { + "groupId": "EG001", + "numAffected": 2, + "numAtRisk": 5 + }, + { + "groupId": "EG002", + "numAffected": 2, + "numAtRisk": 10 + } + ] + }, + { + "term": "Chills", + "organSystem": "General disorders", + "sourceVocabulary": "MedDRA (22.0)", + "assessmentType": "SYSTEMATIC_ASSESSMENT", + "stats": [ + { + "groupId": "EG000", + "numAffected": 0, + "numAtRisk": 3 + }, + { + "groupId": "EG001", + "numAffected": 1, + "numAtRisk": 5 + }, + { + "groupId": "EG002", + "numAffected": 1, + "numAtRisk": 10 + } + ] + }, + { + "term": "Nausea", + "organSystem": "Gastrointestinal disorders", + "sourceVocabulary": "MedDRA (22.0)", + "assessmentType": "SYSTEMATIC_ASSESSMENT", + "stats": [ + { + "groupId": "EG000", + "numAffected": 0, + "numAtRisk": 3 + }, + { + "groupId": "EG001", + "numAffected": 1, + "numAtRisk": 5 + }, + { + "groupId": "EG002", + "numAffected": 1, + "numAtRisk": 10 + } + ] + }, + { + "term": "Joint Pain", + "organSystem": "Musculoskeletal and connective tissue disorders", + "sourceVocabulary": "MedDRA (22.0)", + "assessmentType": "SYSTEMATIC_ASSESSMENT", + "stats": [ + { + "groupId": "EG000", + "numAffected": 0, + "numAtRisk": 3 + }, + { + "groupId": "EG001", + "numAffected": 1, + "numAtRisk": 5 + }, + { + "groupId": "EG002", + "numAffected": 1, + "numAtRisk": 10 + } + ] + } + ] + }, + "moreInfoModule": { + "certainAgreement": { + "piSponsorEmployee": true + }, + "pointOfContact": { + "title": "Martin Gaudinski, MD", + "organization": "Vaccine Research Center, National Institute of Allergy and Infectious Diseases, National Institutes of Health", + "email": "martin.gaudinski@nih.gov", + "phone": "301-451-8715" + } + } + }, + "documentSection": { + "largeDocumentModule": { + "largeDocs": [ + { + "typeAbbrev": "Prot_SAP_ICF", + "hasProtocol": true, + "hasSap": true, + "hasIcf": true, + "label": "Study Protocol, Statistical Analysis Plan, and Informed Consent Form", + "date": "2019-01-14", + "uploadDate": "2020-02-04T13:07", + "filename": "Prot_SAP_ICF_002.pdf", + "size": 18087809 + } + ] + } + }, + "derivedSection": { + "miscInfoModule": { + "versionHolder": "2024-01-24", + "modelPredictions": { + "bmiLimits": { + "minBmi": 0.00, + "maxBmi": 101.00 + } + } + }, + "conditionBrowseModule": { + "browseLeaves": [ + { + "id": "M17212", + "name": "Virus Diseases", + "relevance": "LOW" + }, + { + "id": "M8154", + "name": "Fever", + "relevance": "LOW" + }, + { + "id": "M2455", + "name": "Hyperthermia", + "relevance": "LOW" + }, + { + "id": "M20828", + "name": "Hemorrhagic Fever, Ebola", + "relevance": "LOW" + }, + { + "id": "M9258", + "name": "Hemorrhagic Fevers, Viral", + "relevance": "LOW" + }, + { + "id": "T2014", + "name": "Ebola Virus Disease", + "relevance": "LOW" + }, + { + "id": "T5864", + "name": "Viral Hemorrhagic Fever", + "relevance": "LOW" + } + ], + "browseBranches": [ + { + "abbrev": "BC01", + "name": "Infections" + }, + { + "abbrev": "All", + "name": "All Conditions" + }, + { + "abbrev": "BC23", + "name": "Symptoms and General Pathology" + }, + { + "abbrev": "BC26", + "name": "Wounds and Injuries" + }, + { + "abbrev": "Rare", + "name": "Rare Diseases" + } + ] + }, + "interventionBrowseModule": { + "meshes": [ + { + "id": "C000711947", + "term": "Ansuvimab" + } + ], + "ancestors": [ + { + "id": "D000000998", + "term": "Antiviral Agents" + }, + { + "id": "D000000890", + "term": "Anti-Infective Agents" + } + ], + "browseLeaves": [ + { + "id": "M3915", + "name": "Antibodies", + "relevance": "LOW" + }, + { + "id": "M9874", + "name": "Immunoglobulins", + "relevance": "LOW" + }, + { + "id": "M3920", + "name": "Antibodies, Monoclonal", + "relevance": "LOW" + }, + { + "id": "M18807", + "name": "Immunoglobulins, Intravenous", + "relevance": "LOW" + }, + { + "id": "M340754", + "name": "polysaccharide-K", + "relevance": "LOW" + }, + { + "id": "M266286", + "name": "Ansuvimab", + "asFound": "Tretinoin Cream", + "relevance": "HIGH" + }, + { + "id": "M4004", + "name": "Antiviral Agents", + "relevance": "LOW" + }, + { + "id": "M3904", + "name": "Anti-Infective Agents", + "relevance": "LOW" + } + ], + "browseBranches": [ + { + "abbrev": "All", + "name": "All Drugs and Chemicals" + }, + { + "abbrev": "Infe", + "name": "Anti-Infective Agents" + }, + { + "abbrev": "ANeo", + "name": "Antineoplastic Agents" + } + ] + } + }, + "hasResults": true +} diff --git a/test/data/clinicaltrialsgov.NCT03478891.xml b/test/data/clinicaltrialsgov.NCT03478891.xml deleted file mode 100644 index 4fcbc266..00000000 --- a/test/data/clinicaltrialsgov.NCT03478891.xml +++ /dev/null @@ -1,404 +0,0 @@ - - - - - ClinicalTrials.gov processed this data on July 24, 2019 - Link to the current ClinicalTrials.gov record. - https://clinicaltrials.gov/show/NCT03478891 - - - 180069 - 18-I-0069 - NCT03478891 - - Safety and Pharmacokinetics of a Human Monoclonal Antibody, VRC-EBOMAB092-00-AB (MAb114), Administered Intravenously to Healthy Adults - VRC 608: A Phase I, Open-Label, Dose-Escalation Study of the Safety and Pharmacokinetics of a Human Monoclonal Antibody, VRC-EBOMAB092-00-AB (MAb114), Administered Intravenously to Healthy Adults - - - National Institute of Allergy and Infectious Diseases (NIAID) - NIH - - - The Emmes Company, LLC - Industry - - - National Institutes of Health Clinical Center (CC) - - No - Yes - No - - - - Background: - - Ebola is a virus that has infected and killed people mostly in West Africa. There is no - treatment or prevention for it, but several drugs are being studied. Researchers want to test - the drug MAb114 in healthy people not exposed to Ebola to see whether it can be used for - Ebola treatment in people who are infected in the future. This trial will not expose - volunteers to the Ebola virus. - - Objectives: - - To see if MAb114 is safe and how a person's body responds to it. - - Eligibility: - - Healthy adults ages 18-60 who weigh 220.5 pounds or less - - Design: - - Participants will be screened under protocol NIH 11-I-0164 with: - - - Medical history - - - Physical exam - - - Blood or urine tests - - Participants will have a first 8- to10-hour visit. They will get MAb114 by IV infusion. For - this, a thin tube will be placed in an arm vein. They may get an IV line in their other arm - to collect blood. Blood will be taken many times before and after the infusion. Participants - may have a urine test. - - Participants will get a thermometer to check their temperature for 3 days after they get - MAb114. They will record their highest temperature and any symptoms. - - Participants will have about 14 more study visits over 6 months. At each visit, they will - have blood taken and be checked for any health changes. They will talk about how they are - feeling and if they have taken any medications. - - At the end of the 6 months, participants may be invited to take part in another study for - follow-up sample collection. - - - - - VRC 608: A Phase I, Open-Label, Dose-Escalation Study of the Safety and Pharmacokinetics of a - Human Monoclonal Antibody, VRC-EBOMAB092-00-AB (MAb114), Administered Intravenously to - Healthy Adults - - Study Design: VRC 608 is the first-in-human Phase I study to examine safety, tolerability and - pharmacokinetics of the monoclonal antibody (MAb), VRC-EBOMAB092-00-AB (MAb114). MAb114 will - be administered as a single dose. The hypotheses are: 1) MAb114 administration to healthy - adults will be safe by the intravenous (IV) route; and 2) MAb114 will be detectable in human - sera with a definable half-life. The primary objectives are to evaluate the safety and - tolerability of MAb114 in healthy adults. Secondary objectives will evaluate the - pharmacokinetics of MAb114 and the potential to detect an anti-drug antibody response to - MAb114. - - Product Description: MAb114 is a human IgG1 MAb targeted to the Zaire ebolavirus (EBOV) - glycoprotein (GP). It was developed by the VRC/NIAID/NIH and manufactured at Cook Pharmica - LLC d.b.a. Catalent Indiana, LLC (Bloomington, IN) under current Good Manufacturing Practice - (cGMP) regulations. MAb114 is supplied as a lyophilized product in a glass vial at 400 mg per - vial with target overfill to 425 mg per vial. - - Subjects: Up to 30 healthy adults, 18-60 years of age. - - Study Plan: This is an open-label, dose-escalation study of MAb114 administered by IV - infusion at dosages of 5, 25 and 50 mg/kg (Groups 1-3). Enrollment will begin with the lowest - dose group. Following the first product administration in each group, the study team will - wait at least 3 days before administering MAb114 to a second subject within the same group. - Dose-escalation evaluations will occur to ensure the safety data support proceeding to the - higher doses. Solicited reactogenicity following product administration will be evaluated - using a 3-day diary. Assessment of safety will include clinical observation and monitoring of - serum hematological and chemical parameters at defined timepoints throughout the study. - - - Group 1: 3 subjects; 5 mg/kg IV - - - Group 2: 5 subjects; 25 mg/kg IV - - - Group 3: 10 subjects; 50 mg/kg IV - - - Total* 18 subjects - - - A minimum of 18 subjects will be enrolled. Enrollment up to a total of 30 subjects - is permitted if additional subjects are necessary for safety or pharmacokinetic - (PK) evaluations. - - Study Duration: Subjects will be followed for 24 weeks after the study product - administration. - - - Completed - May 16, 2018 - March 20, 2019 - March 20, 2019 - Phase 1 - Interventional - Yes - - Non-Randomized - Sequential Assignment - Treatment - None (Open Label) - - - Number of Subjects Experiencing Infusion Reaction During Product Administration - About 30 minutes of product administration - Possible infusion reaction symptoms: unusually tired/feeling unwell, muscles aches, headache, chills, rigors, nausea, fever, joint pain, urticaria/rash, and pruritus. Also information was collected if administration was slowed down or stopped for reactogenicity reasons. - - - Number of Subjects Reporting Systemic Reactogenicity Signs and Symptoms Within 3 Days of Product Administration - 3 days after product administration - Subjects recorded 3-day systemic reactogenicity symptoms in a diary after study product administration. Solicited systemic symptoms include: unusually tired/feeling unwell, muscles aches, headache, chills, nausea, fever and joint pain. Subjects recorded highest measured temperature daily. Clinicians reviewed the diary with the subject and collected resolution information for any symptoms that were not resolved within 3 days. Subjects were counted once for each symptom at the worst severity if they indicated experiencing the symptom at any severity during the reporting period. The number reported for "Any Systemic Symptom" is the number of subjects experiencing any systemic symptom as reported at the worst severity. Solicited reactogenicity was recorded without an attribution assessment. Grading was done by Division of AIDS Table for Grading the Severity of Adult and Pediatric Adverse Events Version 2.1. - - - Number of Subjects Reporting Local Reactogenicity Signs and Symptoms Within 7 Days of Product Administration - 7 days after product administration - Local reactogenicity symptoms were assessed and recorded by clinicians. Solicited local symptoms include pain/tenderness, swelling, redness, bruising, and pruritus (itchiness) at the product administration site. Clinicians assessed the study product administration site for local symptoms on the day of product administration after completion of the administration and on Days 1, 2 and 7 post administration. Subjects were counted once for each symptom at the worst severity if they experienced the symptom at any severity during the reporting period. If symptoms were experienced, clinicians collected resolution information for any symptom that wasn't resolved within 7 days. The number reported for "Any Local Symptom" is the number of subjects reporting any local symptom as reported at the worst severity. Solicited reactogenicity was recorded without attribution assessment. Grading was done by Division of AIDS Table for Grading the Severity of Adult and Pediatric Adverse Events Version 2.1. - - - Number of Subjects Reporting 1 or More Unsolicited Non-Serious Adverse Events - Through 24 weeks after product administration - Unsolicited adverse events (AEs) collected during the period from study product administration at Day 0 through 28 days after product administration. After the indicated time period through the last expected study visit at 24 weeks after product administration, only new chronic medical conditions collected as unsolicited AEs. -The number reported is the number of subjects who experienced at least one AE in the reporting period. A subject with multiple experiences of the same event is counted once using the event of worst severity. The relationship between an AE and the product was assessed by the investigator on the basis of his or her clinical judgment and the protocol-specific definitions of Related - There is a reasonable possibility that the AE may be related to the study product and Not Related - There is not a reasonable possibility that the AE is related to the study product. - - - Number of Subjects Reporting Serious Adverse Events - Through 24 weeks after product administration - Serious adverse events (SAEs) collected during the period from study product administration at Day 0 through 24 weeks after product administration. Grading done by Division of AIDS Table for Grading the Severity of Adult and Pediatric Adverse Events Version 2.1. The relationship between a SAE and the product was assessed by the investigator on the basis of his or her clinical judgment and the protocol-specific definitions of Related - There is a reasonable possibility that the SAE may be related to the study product and Not Related - There is not a reasonable possibility that the SAE is related to the study product. - - - Maximum Observed Serum Concentration (Cmax) of MAb114 - Pre-infusion (baseline), end of infusion (0h), 1 hr, 3 hr, 6 hr, 24 hr, 48 hr and 7-28 days post-infusion - Cmax is the peak serum concentration that MAb114 achieves after it has been administered; it is determined as a maximum value on the summary pharmacokinetic (PK) curve for each study group. - - - Time to Reach Maximum Observed Serum Concentration (Tmax) of MAb114 - Pre-infusion (baseline), end of infusion (0h), 1 hr, 3 hr, 6 hr, 24 hr, 48 hr and 7-28 days post-infusion - Tmax is the time it takes to reach Cmax of MAb114 after it has been administered; it is determined based on the summary PK curve for each study group. - - - Mean Serum Concentration of MAb114 - Pre-infusion (baseline), end of infusion (0h), 1 hr, 3 hr, 6 hr, 24 hr, 48 hr and 7-28 days post-infusion - The mean of individual subject MAb114 serum concentrations by administered dose group - - - Overall IV Half-life (T1/2) of MAb114 - Pre-infusion (baseline), end of infusion (0h), 1 hr, 3 hr, 6 hr, 24 hr, 48 hr and 7-56 days post-infusion - Half-life (T1/2) is the time required for half of the drug to be eliminated from the serum. - - - Area Under the Curve (AUC0-28D) - Pre-infusion (baseline), end of infusion (0h), 1 hr, 3 hr, 6 hr, 24 hr, 48 hr and 7-28 days post-infusion - The AUC0-28D represents the total drug exposure in 28 days after MAb114 administration; it is determined based on the summary PK curve for each group. - - - Volume of Distribution (Vd) at Steady-state - Pre-infusion (baseline), end of infusion (0h), 1 hr, 3 hr, 6 hr, 24 hr, 48 hr and 7-28 days post-infusion - Theoretical volume that would be necessary to contain the total amount of administered drug at the same concentration as observed in plasma. It represents the degree to which a drug is distributed in body tissue rather than the plasma and calculated based in the PK curve for each study group. - - - MAb114 Clearance Rate - Pre-infusion (baseline), end of infusion (0h), 1 hr, 3 hr, 6 hr, 24 hr, 48 hr and 7-28 days post-infusion - Rate of MAb114 elimination divided by the plasma MAb114 concentration; determined based on the summary PK curve for each study group. - - - Number of Subjects Who Produced Anti-drug Antibodies to MAb114 - Days 28 and 56 post-infusion - Serum samples collected 28 days and 56 days after MAb114 administration - - 3 - 19 - Healthy Adult Immune Responses to Vaccine - - Group 1: 5 mg/kg IV - Experimental - Group 1 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 5 mg/kg. - - - Group 2: 25 mg/kg IV - Experimental - Group 2 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 25 mg/kg. - - - Group 3: 50 mg/kg IV - Experimental - Group 3 subjects received a single IV infusion of a Human Monoclonal Antibody (MAb), VRC-EBOMAB092-00-AB (MAb114), on Day 0 at a dose of 50 mg/kg. - - - Biological - VRC-EBOMAB092-00-AB (MAb114) - VRC-EBOMAB092-00-AB (MAb114) is a human immunoglobulin (IgG1) monoclonal antibody (MAb) targeted to the Zaire ebolavirus (EBOV) glycoprotein (GP). - Group 1: 5 mg/kg IV - Group 2: 25 mg/kg IV - Group 3: 50 mg/kg IV - - - - - INCLUSION CRITERIA: - - A volunteer must meet all of the following criteria: - - - Able and willing to complete the informed consent process. - - - Available for clinical follow-up through the last study visit. - - - 18 to 60 years of age. - - - In good general health without clinically significant medical history. - - - Willing to have blood samples collected, stored indefinitely, and used for research - purposes. - - - Able to provide proof of identity to the satisfaction of the study clinician - completing the enrollment process. - - - Physical examination without clinically significant findings within the 84 days prior - to enrollment. - - - Have screening laboratory values within 84 days prior to enrollment that meet the - following criteria: - - - White Blood Cell (WBC) 2,500-12,000/mm^3 - - - WBC differential either within institutional normal range or accompanied by the - Principal Investigator (PI) or designee approval - - - Platelets = 125,000 - 400,000/mm^3 - - - Hemoglobin within institutional normal range or accompanied by the PI or designee - approval - - - Creatinine less than or equal to 1.1 x upper limit of normal (ULN) - - - Alanine aminotransferase (ALT) less than or equal to 1.25 x ULN - - - Negative for human immunodeficiency virus (HIV) infection by a Food and Drug - Administration (FDA) approved method of detection - - - Negative for Hepatitis B core antibody (HBcAb) and Hepatitis C virus antibody - (HCV Ab) - - - Criteria applicable to women of childbearing potential: - - - If a woman is of reproductive potential and sexually active with a male partner, - then she agrees to use an effective means of birth control from the time of study - enrollment until the last study visit, or to be monogamous with a partner who has - had a vasectomy. - - - Negative human chorionic gonadotropin (beta-HCG) pregnancy test (urine or serum) - on day of enrollment and product administration. - - EXCLUSION CRITERIA: - - A volunteer will be excluded from study participation if one or more of the following - conditions apply: - - - Previous receipt of a licensed or investigational monoclonal antibody or Ebola - vaccine. - - - Weight >100 kg. - - - Any history of a severe allergic reaction with generalized urticaria, angioedema or - anaphylaxis prior to enrollment that has a reasonable risk of recurrence during the - study. - - - Hypertension that is not well controlled. - - - Woman who is breast-feeding, or planning to become pregnant during study - participation. - - - Receipt of any investigational study product within 28 days prior to enrollment. - - - Any other chronic or clinically significant medical condition that in the opinion of - the investigator would jeopardize the safety or rights of the volunteer, including but - not limited to: diabetes mellitus type I, chronic hepatitis; OR clinically significant - forms of: drug or alcohol abuse, asthma, autoimmune disease, psychiatric disorders, - heart disease, or cancer. - - - Bleeding disorder diagnosed by a doctor (e.g. factor deficiency, coagulopathy, or - platelet disorder requiring special precautions) or significant bruising or bleeding - difficulties with intramuscular (IM) injections or blood draws. - - - Use of angiotensin-converting enzyme (ACE) inhibitors or other potential nephrotoxins. - - - All - 18 Years - 60 Years - Accepts Healthy Volunteers - - - Martin R Gaudinski, M.D. - Principal Investigator - National Institute of Allergy and Infectious Diseases (NIAID) - - - - National Institutes of Health Clinical Center -
- Bethesda - Maryland - 20892 - United States -
-
-
- - United States - - - https://clinicalstudies.info.nih.gov/cgi/detail.cgi?A_2018-I-0069.html - NIH Clinical Center Detailed Web Page - - - https://www.nih.gov/news-events/news-releases/investigational-monoclonal-antibody-treat-ebola-safe-adults - NIH Press Release - - - Corti D, Misasi J, Mulangu S, Stanley DA, Kanekiyo M, Wollen S, Ploquin A, Doria-Rose NA, Staupe RP, Bailey M, Shi W, Choe M, Marcus H, Thompson EA, Cagigi A, Silacci C, Fernandez-Rodriguez B, Perez L, Sallusto F, Vanzetta F, Agatic G, Cameroni E, Kisalu N, Gordon I, Ledgerwood JE, Mascola JR, Graham BS, Muyembe-Tamfun JJ, Trefry JC, Lanzavecchia A, Sullivan NJ. Protective monotherapy against lethal Ebola virus infection by a potently neutralizing antibody. Science. 2016 Mar 18;351(6279):1339-42. doi: 10.1126/science.aad5224. Epub 2016 Feb 25. - 26917593 - - - Misasi J, Gilman MS, Kanekiyo M, Gui M, Cagigi A, Mulangu S, Corti D, Ledgerwood JE, Lanzavecchia A, Cunningham J, Muyembe-Tamfun JJ, Baxa U, Graham BS, Xiang Y, Sullivan NJ, McLellan JS. Structural and molecular basis for Ebola virus neutralization by protective human antibodies. Science. 2016 Mar 18;351(6279):1343-6. doi: 10.1126/science.aad6117. Epub 2016 Feb 25. - 26917592 - - - Gaudinski MR, Coates EE, Novik L, Widge A, Houser KV, Burch E, Holman LA, Gordon IJ, Chen GL, Carter C, Nason M, Sitar S, Yamshchikov G, Berkowitz N, Andrews C, Vazquez S, Laurencot C, Misasi J, Arnold F, Carlton K, Lawlor H, Gall J, Bailer RT, McDermott A, Capparelli E, Koup RA, Mascola JR, Graham BS, Sullivan NJ, Ledgerwood JE; VRC 608 Study team. Safety, tolerability, pharmacokinetics, and immunogenicity of the therapeutic monoclonal antibody mAb114 targeting Ebola virus glycoprotein (VRC 608): an open-label phase 1 study. Lancet. 2019 Mar 2;393(10174):889-898. doi: 10.1016/S0140-6736(19)30036-4. Epub 2019 Jan 24. - 30686586 - - June 2019 - March 21, 2018 - March 24, 2018 - March 27, 2018 - October 9, 2018 - October 9, 2018 - October 11, 2018 - June 26, 2019 - June 26, 2019 - July 15, 2019 - - Sponsor - - Ebola Virus - Immune Response - Filovirus - Ebola Hemorrhagic Fever - First in Human - - - Antibodies - Immunoglobulins - Antibodies, Monoclonal - Antineoplastic Agents, Immunological - - - No - - - - Study Protocol, Statistical Analysis Plan, and Informed Consent Form - Yes - Yes - Yes - January 14, 2019 - https://ClinicalTrials.gov/ProvidedDocs/91/NCT03478891/Prot_SAP_ICF_001.pdf - - - -
diff --git a/test/data/ensembl_biomart_export_ENSG00000139618.tsv b/test/data/ensembl_biomart_export_ENSG00000139618.tsv index 8cba445f..f0aa0d0c 100644 --- a/test/data/ensembl_biomart_export_ENSG00000139618.tsv +++ b/test/data/ensembl_biomart_export_ENSG00000139618.tsv @@ -1,5 +1,5 @@ -Gene stable ID Gene stable ID version Transcript stable ID Transcript stable ID version HGNC ID RefSeq mRNA ID Gene description Gene name Source of gene name -ENSG00000139618 ENSG00000139618.17 ENST00000544455 ENST00000544455.6 HGNC:1101 BRCA2 DNA repair associated [Source:HGNC Symbol;Acc:HGNC:1101] BRCA2 HGNC Symbol -ENSG00000139618 ENSG00000139618.17 ENST00000530893 ENST00000530893.6 HGNC:1101 BRCA2 DNA repair associated [Source:HGNC Symbol;Acc:HGNC:1101] BRCA2 HGNC Symbol -ENSG00000139618 ENSG00000139618.17 ENST00000380152 ENST00000380152.8 HGNC:1101 NM_000059 BRCA2 DNA repair associated [Source:HGNC Symbol;Acc:HGNC:1101] BRCA2 HGNC Symbol -ENSG00000139618 ENSG00000139618.17 ENST00000680887 ENST00000680887.1 HGNC:1101 BRCA2 DNA repair associated [Source:HGNC Symbol;Acc:HGNC:1101] BRCA2 HGNC Symbol +Gene stable ID Gene stable ID version Transcript stable ID Transcript stable ID version Protein stable ID Protein stable ID version HGNC ID Gene description Gene name Source of gene name +ENSG00000139618 ENSG00000139618.17 ENST00000544455 ENST00000544455.6 ENSP00000439902 ENSP00000439902.1 HGNC:1101 BRCA2 DNA repair associated [Source:HGNC Symbol;Acc:HGNC:1101] BRCA2 HGNC Symbol +ENSG00000139618 ENSG00000139618.17 ENST00000530893 ENST00000530893.6 ENSP00000499438 ENSP00000499438.2 HGNC:1101 BRCA2 DNA repair associated [Source:HGNC Symbol;Acc:HGNC:1101] BRCA2 HGNC Symbol +ENSG00000139618 ENSG00000139618.17 ENST00000380152 ENST00000380152.8 ENSP00000369497 ENSP00000369497.3 HGNC:1101 BRCA2 DNA repair associated [Source:HGNC Symbol;Acc:HGNC:1101] BRCA2 HGNC Symbol +ENSG00000139618 ENSG00000139618.17 ENST00000680887 ENST00000680887.1 ENSP00000505508 ENSP00000505508.1 HGNC:1101 BRCA2 DNA repair associated [Source:HGNC Symbol;Acc:HGNC:1101] BRCA2 HGNC Symbol diff --git a/test/data/ensembl_uploadFile_requests.json b/test/data/ensembl_uploadFile_requests.json index c6387d53..9c0d2ccf 100644 --- a/test/data/ensembl_uploadFile_requests.json +++ b/test/data/ensembl_uploadFile_requests.json @@ -50,58 +50,6 @@ } } }, - { - "opt": { - "body": { - "filters": { - "name": "refseq" - }, - "neighbors": 1, - "target": "Source" - }, - "method": "POST", - "uri": "/query" - }, - "result": { - "metadata": { - "records": 0 - }, - "result": [] - } - }, - { - "opt": { - "body": { - "description": "A comprehensive, integrated, non-redundant, well-annotated set of reference sequences including genomic, transcript, and protein.", - "displayName": "RefSeq", - "longName": "RefSeq: NCBI Reference Sequence Database", - "name": "refseq", - "url": "https://www.ncbi.nlm.nih.gov/refseq", - "usage": "https://www.ncbi.nlm.nih.gov/home/about/policies" - }, - "method": "POST", - "uri": "/sources" - }, - "result": { - "result": { - "@class": "Source", - "sort": 99999, - "description": "a comprehensive, integrated, non-redundant, well-annotated set of reference sequences including genomic, transcript, and protein.", - "displayName": "RefSeq", - "longName": "refseq: ncbi reference sequence database", - "name": "refseq", - "url": "https://www.ncbi.nlm.nih.gov/refseq", - "usage": "https://www.ncbi.nlm.nih.gov/home/about/policies", - "createdBy": "#32:0", - "updatedBy": "#32:0", - "uuid": "f319e234-13b1-4841-b049-e48aacf2644a", - "createdAt": 1653589823081, - "updatedAt": 1653589823081, - "@rid": "#42:0", - "@version": 1 - } - } - }, { "opt": { "body": { @@ -177,18 +125,13 @@ "filters": { "AND": [ { - "source": { - "filters": { - "name": "refseq" - }, - "target": "Source" - } + "source": "#41:0" }, { - "dependency": null + "biotype": "transcript" }, { - "deprecated": false + "dependency": null } ] }, @@ -208,85 +151,26 @@ "result": [] } }, - { - "opt": { - "body": { - "filters": { - "name": "refseq" - }, - "neighbors": 1, - "target": "Source" - }, - "method": "POST", - "uri": "/query" - }, - "result": { - "metadata": { - "records": 1 - }, - "result": [ - { - "createdAt": 1653589823081, - "updatedBy": { - "createdAt": 1653589817005, - "lastLoginAt": 1653589823040, - "name": "mlemieux", - "groups": [ - "#25:0" - ], - "signedLicenseAt": 1653589817002, - "firstLoginAt": 1653589823040, - "uuid": "d3ccc0e1-a51d-4086-abd4-a35ee8f8b6fa", - "loginCount": 1, - "@rid": "#32:0", - "@class": "User" - }, - "createdBy": { - "createdAt": 1653589817005, - "lastLoginAt": 1653589823040, - "name": "mlemieux", - "groups": [ - "#25:0" - ], - "signedLicenseAt": 1653589817002, - "firstLoginAt": 1653589823040, - "uuid": "d3ccc0e1-a51d-4086-abd4-a35ee8f8b6fa", - "loginCount": 1, - "@rid": "#32:0", - "@class": "User" - }, - "displayName": "RefSeq", - "usage": "https://www.ncbi.nlm.nih.gov/home/about/policies", - "name": "refseq", - "description": "a comprehensive, integrated, non-redundant, well-annotated set of reference sequences including genomic, transcript, and protein.", - "sort": 99999, - "uuid": "f319e234-13b1-4841-b049-e48aacf2644a", - "url": "https://www.ncbi.nlm.nih.gov/refseq", - "longName": "refseq: ncbi reference sequence database", - "updatedAt": 1653589823081, - "@rid": "#42:0", - "@class": "Source" - } - ] - } - }, { "opt": { "body": { "filters": { "AND": [ { - "sourceId": "NM_000059" + "source": "#41:0" }, { - "source": "#42:0" + "biotype": "protein" }, { - "sourceIdVersion": null + "dependency": null } ] }, - "neighbors": 1, + "limit": 1000, + "neighbors": 0, + "returnProperties": null, + "skip": 0, "target": "Feature" }, "method": "POST", @@ -299,45 +183,12 @@ "result": [] } }, - { - "opt": { - "body": { - "biotype": "transcript", - "displayName": "NM_000059", - "sourceId": "NM_000059", - "sourceIdVersion": null, - "source": "#42:0" - }, - "method": "POST", - "uri": "/features" - }, - "result": { - "result": { - "@class": "Feature", - "deprecated": false, - "alias": false, - "biotype": "transcript", - "displayName": "NM_000059", - "sourceId": "nm_000059", - "sourceIdVersion": null, - "source": "#42:0", - "createdBy": "#32:0", - "updatedBy": "#32:0", - "uuid": "1703537d-b15a-4a95-a3f8-575939e5c89d", - "createdAt": 1653589823632, - "updatedAt": 1653589823632, - "name": "nm_000059", - "@rid": "#137:0", - "@version": 1 - } - } - }, { "opt": { "body": { "biotype": "gene", "source": "#41:0", - "sourceId": "ENSG00000139618", + "sourceId": "ensg00000139618", "sourceIdVersion": "17" }, "method": "POST", @@ -358,7 +209,7 @@ "createdAt": 1653589823660, "updatedAt": 1653589823660, "name": "ensg00000139618", - "displayName": "ensg00000139618", + "displayName": "ENSG00000139618.17", "@rid": "#138:0", "@version": 1 } @@ -369,7 +220,7 @@ "body": { "biotype": "gene", "source": "#41:0", - "sourceId": "ENSG00000139618", + "sourceId": "ensg00000139618", "sourceIdVersion": null }, "method": "POST", @@ -390,7 +241,7 @@ "createdAt": 1653589823670, "updatedAt": 1653589823670, "name": "ensg00000139618", - "displayName": "ensg00000139618", + "displayName": "ENSG00000139618", "@rid": "#139:0", "@version": 1 } @@ -425,7 +276,7 @@ "body": { "biotype": "transcript", "source": "#41:0", - "sourceId": "ENST00000544455", + "sourceId": "enst00000544455", "sourceIdVersion": "6" }, "method": "POST", @@ -446,7 +297,7 @@ "createdAt": 1653589823689, "updatedAt": 1653589823689, "name": "enst00000544455", - "displayName": "enst00000544455", + "displayName": "ENST00000544455.6", "@rid": "#140:0", "@version": 1 } @@ -457,7 +308,7 @@ "body": { "biotype": "transcript", "source": "#41:0", - "sourceId": "ENST00000544455", + "sourceId": "enst00000544455", "sourceIdVersion": null }, "method": "POST", @@ -478,7 +329,7 @@ "createdAt": 1653589823699, "updatedAt": 1653589823699, "name": "enst00000544455", - "displayName": "enst00000544455", + "displayName": "ENST00000544455", "@rid": "#137:1", "@version": 1 } @@ -487,23 +338,23 @@ { "opt": { "body": { - "in": "#140:0", + "in": "#139:0", "out": "#137:1", "source": "#41:0" }, "method": "POST", - "uri": "/generalizationof" + "uri": "/elementof" }, "result": { "result": { - "@class": "GeneralizationOf", + "@class": "ElementOf", "out": "#137:1", - "in": "#140:0", + "in": "#139:0", "source": "#41:0", "createdBy": "#32:0", - "uuid": "90cc8c10-3a43-49ef-880d-b4294bfc2604", - "createdAt": 1653589823707, - "@rid": "#98:0", + "uuid": "a469002c-185b-471c-983a-f8a80949916d", + "createdAt": 1653589823717, + "@rid": "#93:0", "@version": 1 } } @@ -511,23 +362,23 @@ { "opt": { "body": { - "in": "#139:0", + "in": "#140:0", "out": "#137:1", "source": "#41:0" }, "method": "POST", - "uri": "/elementof" + "uri": "/generalizationof" }, "result": { "result": { - "@class": "ElementOf", + "@class": "GeneralizationOf", "out": "#137:1", - "in": "#139:0", + "in": "#140:0", "source": "#41:0", "createdBy": "#32:0", - "uuid": "a469002c-185b-471c-983a-f8a80949916d", - "createdAt": 1653589823717, - "@rid": "#93:0", + "uuid": "90cc8c10-3a43-49ef-880d-b4294bfc2604", + "createdAt": 1653589823707, + "@rid": "#98:0", "@version": 1 } } @@ -559,83 +410,87 @@ { "opt": { "body": { - "filters": { - "AND": [ - { - "source": { - "filters": { - "name": "hgnc" - }, - "target": "Source" - } - }, - { - "sourceId": "hgnc:1101" - } - ] - }, - "neighbors": 1, - "target": "Feature" + "biotype": "protein", + "source": "#41:0", + "sourceId": "ensp00000439902", + "sourceIdVersion": "1" }, "method": "POST", - "uri": "/query" + "uri": "/features" }, "result": { - "metadata": { - "records": 0 - }, - "result": [] + "result": { + "@class": "Feature", + "deprecated": false, + "alias": false, + "biotype": "protein", + "source": "#41:0", + "sourceId": "ensp00000439902", + "sourceIdVersion": "1", + "createdBy": "#32:0", + "updatedBy": "#32:0", + "uuid": "7be71924-5dc6-40ef-9289-153184ed6eba", + "createdAt": 1653589823689, + "updatedAt": 1653589823689, + "name": "ensp00000439902", + "displayName": "ENSP00000439902.1", + "@rid": "#145:1", + "@version": 1 + } } }, { "opt": { "body": { - "filters": { - "name": "hgnc" - }, - "neighbors": 1, - "target": "Source" + "biotype": "protein", + "source": "#41:0", + "sourceId": "ensp00000439902", + "sourceIdVersion": null }, "method": "POST", - "uri": "/query" + "uri": "/features" }, "result": { - "metadata": { - "records": 0 - }, - "result": [] + "result": { + "@class": "Feature", + "deprecated": false, + "alias": false, + "biotype": "protein", + "source": "#41:0", + "sourceId": "ensp00000439902", + "sourceIdVersion": null, + "createdBy": "#32:0", + "updatedBy": "#32:0", + "uuid": "df4d32df-e526-402e-8eec-ec5284108297", + "createdAt": 1653589823699, + "updatedAt": 1653589823699, + "name": "ensp00000439902", + "displayName": "ENSP00000439902", + "@rid": "#146:1", + "@version": 1 + } } }, { "opt": { "body": { - "description": "The HGNC is responsible for approving unique symbols and names for human loci, including protein coding genes, ncRNA genes and pseudogenes, to allow unambiguous scientific communication.", - "displayName": "HGNC", - "longName": "HUGO Gene Nomenclature Committee", - "name": "hgnc", - "sort": 2, - "url": "https://www.genenames.org/about", - "usage": "https://www.ebi.ac.uk/about/terms-of-use" + "in": "#137:1", + "out": "#146:1", + "source": "#41:0" }, "method": "POST", - "uri": "/sources" + "uri": "/elementof" }, "result": { "result": { - "@class": "Source", - "sort": 2, - "description": "the hgnc is responsible for approving unique symbols and names for human loci, including protein coding genes, ncrna genes and pseudogenes, to allow unambiguous scientific communication.", - "displayName": "HGNC", - "longName": "hugo gene nomenclature committee", - "name": "hgnc", - "url": "https://www.genenames.org/about", - "usage": "https://www.ebi.ac.uk/about/terms-of-use", + "@class": "ElementOf", + "out": "#146:1", + "in": "#137:1", + "source": "#41:0", "createdBy": "#32:0", - "updatedBy": "#32:0", - "uuid": "7b4d8402-c3cb-462b-b1bd-6f0cb2d4ed14", - "createdAt": 1653589829457, - "updatedAt": 1653589829457, - "@rid": "#43:0", + "uuid": "23cfd12e-429f-480f-9a41-1e0229a731b5", + "createdAt": 1653589823727, + "@rid": "#66:0", "@version": 1 } } @@ -643,14 +498,146 @@ { "opt": { "body": { - "filters": { - "name": "ensembl" - }, - "neighbors": 1, - "target": "Source" + "in": "#145:1", + "out": "#146:1", + "source": "#41:0" }, "method": "POST", - "uri": "/query" + "uri": "/generalizationof" + }, + "result": { + "result": { + "@class": "GeneralizationOf", + "out": "#146:1", + "in": "#145:1", + "source": "#41:0", + "createdBy": "#32:0", + "uuid": "90cc8c10-3a43-49ef-880d-b4294bfc2604", + "createdAt": 1653589823707, + "@rid": "#75:0", + "@version": 1 + } + } + }, + { + "opt": { + "body": { + "in": "#140:0", + "out": "#145:1", + "source": "#41:0" + }, + "method": "POST", + "uri": "/elementof" + }, + "result": { + "result": { + "@class": "ElementOf", + "out": "#145:1", + "in": "#140:0", + "source": "#41:0", + "createdBy": "#32:0", + "uuid": "a469002c-185b-471c-983a-f8a80949916d", + "createdAt": 1653589823717, + "@rid": "#65:0", + "@version": 1 + } + } + }, + { + "opt": { + "body": { + "filters": { + "AND": [ + { + "source": { + "filters": { + "name": "hgnc" + }, + "target": "Source" + } + }, + { + "sourceId": "hgnc:1101" + } + ] + }, + "neighbors": 1, + "target": "Feature" + }, + "method": "POST", + "uri": "/query" + }, + "result": { + "metadata": { + "records": 0 + }, + "result": [] + } + }, + { + "opt": { + "body": { + "filters": { + "name": "hgnc" + }, + "neighbors": 1, + "target": "Source" + }, + "method": "POST", + "uri": "/query" + }, + "result": { + "metadata": { + "records": 0 + }, + "result": [] + } + }, + { + "opt": { + "body": { + "description": "The HGNC is responsible for approving unique symbols and names for human loci, including protein coding genes, ncRNA genes and pseudogenes, to allow unambiguous scientific communication.", + "displayName": "HGNC", + "longName": "HUGO Gene Nomenclature Committee", + "name": "hgnc", + "sort": 2, + "url": "https://www.genenames.org/about", + "usage": "https://www.ebi.ac.uk/about/terms-of-use" + }, + "method": "POST", + "uri": "/sources" + }, + "result": { + "result": { + "@class": "Source", + "sort": 2, + "description": "the hgnc is responsible for approving unique symbols and names for human loci, including protein coding genes, ncrna genes and pseudogenes, to allow unambiguous scientific communication.", + "displayName": "HGNC", + "longName": "hugo gene nomenclature committee", + "name": "hgnc", + "url": "https://www.genenames.org/about", + "usage": "https://www.ebi.ac.uk/about/terms-of-use", + "createdBy": "#32:0", + "updatedBy": "#32:0", + "uuid": "7b4d8402-c3cb-462b-b1bd-6f0cb2d4ed14", + "createdAt": 1653589829457, + "updatedAt": 1653589829457, + "@rid": "#43:0", + "@version": 1 + } + } + }, + { + "opt": { + "body": { + "filters": { + "name": "ensembl" + }, + "neighbors": 1, + "target": "Source" + }, + "method": "POST", + "uri": "/query" }, "result": { "metadata": { @@ -785,7 +772,7 @@ "biotype": "gene" }, { - "sourceId": "ENSG00000139618" + "sourceId": "ensg00000139618" } ] }, @@ -817,7 +804,7 @@ "@rid": "#32:0", "@class": "User" }, - "displayName": "ensg00000139618", + "displayName": "ENSG00000139618", "deprecated": false, "sourceIdVersion": null, "source": { @@ -910,7 +897,7 @@ "@class": "GeneralizationOf" } ], - "displayName": "ensg00000139618", + "displayName": "ENSG00000139618.17", "deprecated": false, "sourceIdVersion": "17", "source": { @@ -1558,7 +1545,7 @@ "body": { "biotype": "transcript", "source": "#41:0", - "sourceId": "ENST00000530893", + "sourceId": "enst00000530893", "sourceIdVersion": "6" }, "method": "POST", @@ -1579,7 +1566,7 @@ "createdAt": 1653589830128, "updatedAt": 1653589830128, "name": "enst00000530893", - "displayName": "enst00000530893", + "displayName": "ENST00000530893.6", "@rid": "#139:3", "@version": 1 } @@ -1590,7 +1577,7 @@ "body": { "biotype": "transcript", "source": "#41:0", - "sourceId": "ENST00000530893", + "sourceId": "enst00000530893", "sourceIdVersion": null }, "method": "POST", @@ -1611,7 +1598,7 @@ "createdAt": 1653589830136, "updatedAt": 1653589830136, "name": "enst00000530893", - "displayName": "enst00000530893", + "displayName": "ENST00000530893", "@rid": "#140:3", "@version": 1 } @@ -1620,23 +1607,23 @@ { "opt": { "body": { - "in": "#139:3", + "in": "#139:0", "out": "#140:3", "source": "#41:0" }, "method": "POST", - "uri": "/generalizationof" + "uri": "/elementof" }, "result": { "result": { - "@class": "GeneralizationOf", + "@class": "ElementOf", "out": "#140:3", - "in": "#139:3", + "in": "#139:0", "source": "#41:0", "createdBy": "#32:0", - "uuid": "c51db109-2d0f-4ddf-b8f6-7177bf862f7c", - "createdAt": 1653589830143, - "@rid": "#100:0", + "uuid": "c53bf883-4edc-433e-b1ca-3e0b7065c196", + "createdAt": 1653589830152, + "@rid": "#95:0", "@version": 1 } } @@ -1644,23 +1631,23 @@ { "opt": { "body": { - "in": "#139:0", + "in": "#139:3", "out": "#140:3", "source": "#41:0" }, "method": "POST", - "uri": "/elementof" + "uri": "/generalizationof" }, "result": { "result": { - "@class": "ElementOf", + "@class": "GeneralizationOf", "out": "#140:3", - "in": "#139:0", + "in": "#139:3", "source": "#41:0", "createdBy": "#32:0", - "uuid": "c53bf883-4edc-433e-b1ca-3e0b7065c196", - "createdAt": 1653589830152, - "@rid": "#95:0", + "uuid": "c51db109-2d0f-4ddf-b8f6-7177bf862f7c", + "createdAt": 1653589830143, + "@rid": "#100:0", "@version": 1 } } @@ -1692,24 +1679,10 @@ { "opt": { "body": { - "in": "#138:0", - "out": "#139:0", - "source": "#41:0" - }, - "method": "POST", - "uri": "/generalizationof" - }, - "result": { - "result": {} - } - }, - { - "opt": { - "body": { - "biotype": "transcript", + "biotype": "protein", "source": "#41:0", - "sourceId": "ENST00000380152", - "sourceIdVersion": "8" + "sourceId": "ensp00000499438", + "sourceIdVersion": "2" }, "method": "POST", "uri": "/features" @@ -1719,18 +1692,18 @@ "@class": "Feature", "deprecated": false, "alias": false, - "biotype": "transcript", + "biotype": "protein", "source": "#41:0", - "sourceId": "enst00000380152", - "sourceIdVersion": "8", + "sourceId": "ensp00000499438", + "sourceIdVersion": "2", "createdBy": "#32:0", "updatedBy": "#32:0", - "uuid": "83959813-cb70-4b29-bf6c-2c615137d5d8", - "createdAt": 1653589830193, - "updatedAt": 1653589830193, - "name": "enst00000380152", - "displayName": "enst00000380152", - "@rid": "#137:4", + "uuid": "3c025f3a-81e5-4516-876a-c6e11c728337", + "createdAt": 1653589830128, + "updatedAt": 1653589830128, + "name": "ensp00000499438", + "displayName": "ENSP00000499438.2", + "@rid": "#147:2", "@version": 1 } } @@ -1738,9 +1711,9 @@ { "opt": { "body": { - "biotype": "transcript", + "biotype": "protein", "source": "#41:0", - "sourceId": "ENST00000380152", + "sourceId": "ensp00000499438", "sourceIdVersion": null }, "method": "POST", @@ -1751,18 +1724,18 @@ "@class": "Feature", "deprecated": false, "alias": false, - "biotype": "transcript", + "biotype": "protein", "source": "#41:0", - "sourceId": "enst00000380152", + "sourceId": "ensp00000499438", "sourceIdVersion": null, "createdBy": "#32:0", "updatedBy": "#32:0", - "uuid": "c978f03e-f948-4ba6-93f4-c2eaf2639e39", - "createdAt": 1653589830202, - "updatedAt": 1653589830202, - "name": "enst00000380152", - "displayName": "enst00000380152", - "@rid": "#138:4", + "uuid": "25ad8bb3-e465-4884-9d30-c19a09d2204c", + "createdAt": 1653589830136, + "updatedAt": 1653589830136, + "name": "ensp00000499438", + "displayName": "ENSP00000499438", + "@rid": "#148:2", "@version": 1 } } @@ -1770,23 +1743,23 @@ { "opt": { "body": { - "in": "#137:4", - "out": "#138:4", + "in": "#140:3", + "out": "#148:2", "source": "#41:0" }, "method": "POST", - "uri": "/generalizationof" + "uri": "/elementof" }, "result": { "result": { - "@class": "GeneralizationOf", - "out": "#138:4", - "in": "#137:4", + "@class": "ElementOf", + "out": "#148:2", + "in": "#140:3", "source": "#41:0", "createdBy": "#32:0", - "uuid": "0b3717ed-776e-442f-ac03-d8a906b11613", - "createdAt": 1653589830214, - "@rid": "#98:1", + "uuid": "696a05c4-0fca-449e-ab06-b1ff838731f4", + "createdAt": 1653589830160, + "@rid": "#68:0", "@version": 1 } } @@ -1794,23 +1767,23 @@ { "opt": { "body": { - "in": "#139:0", - "out": "#138:4", + "in": "#147:2", + "out": "#148:2", "source": "#41:0" }, "method": "POST", - "uri": "/elementof" + "uri": "/generalizationof" }, "result": { "result": { - "@class": "ElementOf", - "out": "#138:4", - "in": "#139:0", + "@class": "GeneralizationOf", + "out": "#148:2", + "in": "#147:2", "source": "#41:0", "createdBy": "#32:0", - "uuid": "4f650139-eacd-47cb-b040-27ff18092f7c", - "createdAt": 1653589830223, - "@rid": "#93:1", + "uuid": "c51db109-2d0f-4ddf-b8f6-7177bf862f7c", + "createdAt": 1653589830143, + "@rid": "#56:0", "@version": 1 } } @@ -1818,8 +1791,8 @@ { "opt": { "body": { - "in": "#138:0", - "out": "#137:4", + "in": "#139:3", + "out": "#147:2", "source": "#41:0" }, "method": "POST", @@ -1828,13 +1801,13 @@ "result": { "result": { "@class": "ElementOf", - "out": "#137:4", - "in": "#138:0", + "out": "#147:2", + "in": "#139:3", "source": "#41:0", "createdBy": "#32:0", - "uuid": "58d664a2-8465-403f-bc30-4a06821ce3e5", - "createdAt": 1653589830232, - "@rid": "#94:1", + "uuid": "c53bf883-4edc-433e-b1ca-3e0b7065c196", + "createdAt": 1653589830152, + "@rid": "#67:0", "@version": 1 } } @@ -1842,111 +1815,285 @@ { "opt": { "body": { - "filters": { - "AND": [ - { - "source": "#42:0" - }, - { - "sourceId": "NM_000059" - }, - { - "sourceIdVersion": null - } - ] - }, - "neighbors": 1, - "target": "Feature" + "in": "#138:0", + "out": "#139:0", + "source": "#41:0" }, "method": "POST", - "uri": "/query" + "uri": "/generalizationof" }, "result": { - "metadata": { - "records": 1 - }, - "result": [ - { - "biotype": "transcript", - "sourceId": "nm_000059", - "updatedBy": { - "createdAt": 1653589817005, - "lastLoginAt": 1653589823040, - "name": "mlemieux", - "groups": [ - "#25:0" - ], - "signedLicenseAt": 1653589817002, - "firstLoginAt": 1653589823040, - "uuid": "d3ccc0e1-a51d-4086-abd4-a35ee8f8b6fa", - "loginCount": 1, - "@rid": "#32:0", - "@class": "User" - }, - "displayName": "NM_000059", - "deprecated": false, - "sourceIdVersion": null, - "source": { - "createdAt": 1653589823081, - "updatedBy": "#32:0", - "createdBy": "#32:0", - "displayName": "RefSeq", - "usage": "https://www.ncbi.nlm.nih.gov/home/about/policies", - "name": "refseq", - "description": "a comprehensive, integrated, non-redundant, well-annotated set of reference sequences including genomic, transcript, and protein.", - "sort": 99999, - "uuid": "f319e234-13b1-4841-b049-e48aacf2644a", - "url": "https://www.ncbi.nlm.nih.gov/refseq", - "longName": "refseq: ncbi reference sequence database", - "updatedAt": 1653589823081, - "@rid": "#42:0", - "@class": "Source" - }, - "uuid": "1703537d-b15a-4a95-a3f8-575939e5c89d", - "createdAt": 1653589823632, - "createdBy": { - "createdAt": 1653589817005, - "lastLoginAt": 1653589823040, - "name": "mlemieux", - "groups": [ - "#25:0" - ], - "signedLicenseAt": 1653589817002, - "firstLoginAt": 1653589823040, - "uuid": "d3ccc0e1-a51d-4086-abd4-a35ee8f8b6fa", - "loginCount": 1, - "@rid": "#32:0", - "@class": "User" - }, - "name": "nm_000059", - "alias": false, - "updatedAt": 1653589823632, - "@rid": "#137:0", - "@class": "Feature" - } - ] + "result": {} + } + }, + { + "opt": { + "body": { + "biotype": "transcript", + "source": "#41:0", + "sourceId": "enst00000380152", + "sourceIdVersion": "8" + }, + "method": "POST", + "uri": "/features" + }, + "result": { + "result": { + "@class": "Feature", + "deprecated": false, + "alias": false, + "biotype": "transcript", + "source": "#41:0", + "sourceId": "enst00000380152", + "sourceIdVersion": "8", + "createdBy": "#32:0", + "updatedBy": "#32:0", + "uuid": "83959813-cb70-4b29-bf6c-2c615137d5d8", + "createdAt": 1653589830193, + "updatedAt": 1653589830193, + "name": "enst00000380152", + "displayName": "ENST00000380152.8", + "@rid": "#137:4", + "@version": 1 + } + } + }, + { + "opt": { + "body": { + "biotype": "transcript", + "source": "#41:0", + "sourceId": "enst00000380152", + "sourceIdVersion": null + }, + "method": "POST", + "uri": "/features" + }, + "result": { + "result": { + "@class": "Feature", + "deprecated": false, + "alias": false, + "biotype": "transcript", + "source": "#41:0", + "sourceId": "enst00000380152", + "sourceIdVersion": null, + "createdBy": "#32:0", + "updatedBy": "#32:0", + "uuid": "c978f03e-f948-4ba6-93f4-c2eaf2639e39", + "createdAt": 1653589830202, + "updatedAt": 1653589830202, + "name": "enst00000380152", + "displayName": "ENST00000380152", + "@rid": "#138:4", + "@version": 1 + } } }, { "opt": { "body": { - "in": "#137:0", + "in": "#139:0", "out": "#138:4", "source": "#41:0" }, "method": "POST", - "uri": "/crossreferenceof" + "uri": "/elementof" }, "result": { "result": { - "@class": "CrossReferenceOf", + "@class": "ElementOf", + "out": "#138:4", + "in": "#139:0", + "source": "#41:0", + "createdBy": "#32:0", + "uuid": "4f650139-eacd-47cb-b040-27ff18092f7c", + "createdAt": 1653589830223, + "@rid": "#93:1", + "@version": 1 + } + } + }, + { + "opt": { + "body": { + "in": "#137:4", + "out": "#138:4", + "source": "#41:0" + }, + "method": "POST", + "uri": "/generalizationof" + }, + "result": { + "result": { + "@class": "GeneralizationOf", "out": "#138:4", - "in": "#137:0", + "in": "#137:4", "source": "#41:0", "createdBy": "#32:0", - "uuid": "5b3d1a81-1453-4464-8cba-4ef3d660c163", - "createdAt": 1653589830261, - "@rid": "#83:0", + "uuid": "0b3717ed-776e-442f-ac03-d8a906b11613", + "createdAt": 1653589830214, + "@rid": "#98:1", + "@version": 1 + } + } + }, + { + "opt": { + "body": { + "in": "#138:0", + "out": "#137:4", + "source": "#41:0" + }, + "method": "POST", + "uri": "/elementof" + }, + "result": { + "result": { + "@class": "ElementOf", + "out": "#137:4", + "in": "#138:0", + "source": "#41:0", + "createdBy": "#32:0", + "uuid": "58d664a2-8465-403f-bc30-4a06821ce3e5", + "createdAt": 1653589830232, + "@rid": "#94:1", + "@version": 1 + } + } + }, + { + "opt": { + "body": { + "biotype": "protein", + "source": "#41:0", + "sourceId": "ensp00000369497", + "sourceIdVersion": "3" + }, + "method": "POST", + "uri": "/features" + }, + "result": { + "result": { + "@class": "Feature", + "deprecated": false, + "alias": false, + "biotype": "protein", + "source": "#41:0", + "sourceId": "ensp00000369497", + "sourceIdVersion": "3", + "createdBy": "#32:0", + "updatedBy": "#32:0", + "uuid": "83959813-cb70-4b29-bf6c-2c615137d5d8", + "createdAt": 1653589830193, + "updatedAt": 1653589830193, + "name": "ensp00000369497", + "displayName": "ENSP00000369497.3", + "@rid": "#149:3", + "@version": 1 + } + } + }, + { + "opt": { + "body": { + "biotype": "protein", + "source": "#41:0", + "sourceId": "ensp00000369497", + "sourceIdVersion": null + }, + "method": "POST", + "uri": "/features" + }, + "result": { + "result": { + "@class": "Feature", + "deprecated": false, + "alias": false, + "biotype": "protein", + "source": "#41:0", + "sourceId": "ensp00000369497", + "sourceIdVersion": null, + "createdBy": "#32:0", + "updatedBy": "#32:0", + "uuid": "c978f03e-f948-4ba6-93f4-c2eaf2639e39", + "createdAt": 1653589830202, + "updatedAt": 1653589830202, + "name": "ensp00000369497", + "displayName": "ENSP00000369497", + "@rid": "#150:3", + "@version": 1 + } + } + }, + { + "opt": { + "body": { + "in": "#138:4", + "out": "#150:3", + "source": "#41:0" + }, + "method": "POST", + "uri": "/elementof" + }, + "result": { + "result": { + "@class": "ElementOf", + "out": "#150:3", + "in": "#138:4", + "source": "#41:0", + "createdBy": "#32:0", + "uuid": "58d664a2-8465-403f-bc30-4a06821ce3e5", + "createdAt": 1653589830232, + "@rid": "#71:1", + "@version": 1 + } + } + }, + { + "opt": { + "body": { + "in": "#149:3", + "out": "#150:3", + "source": "#41:0" + }, + "method": "POST", + "uri": "/generalizationof" + }, + "result": { + "result": { + "@class": "GeneralizationOf", + "out": "#150:3", + "in": "#149:3", + "source": "#41:0", + "createdBy": "#32:0", + "uuid": "0b3717ed-776e-442f-ac03-d8a906b11613", + "createdAt": 1653589830214, + "@rid": "#57:1", + "@version": 1 + } + } + }, + { + "opt": { + "body": { + "in": "#137:4", + "out": "#149:3", + "source": "#41:0" + }, + "method": "POST", + "uri": "/elementof" + }, + "result": { + "result": { + "@class": "ElementOf", + "out": "#149:3", + "in": "#137:4", + "source": "#41:0", + "createdBy": "#32:0", + "uuid": "4f650139-eacd-47cb-b040-27ff18092f7c", + "createdAt": 1653589830223, + "@rid": "#70:1", "@version": 1 } } @@ -1970,7 +2117,7 @@ "body": { "biotype": "transcript", "source": "#41:0", - "sourceId": "ENST00000680887", + "sourceId": "enst00000680887", "sourceIdVersion": "1" }, "method": "POST", @@ -1991,7 +2138,7 @@ "createdAt": 1653589830282, "updatedAt": 1653589830282, "name": "enst00000680887", - "displayName": "enst00000680887", + "displayName": "ENST00000680887.1", "@rid": "#139:4", "@version": 1 } @@ -2002,7 +2149,7 @@ "body": { "biotype": "transcript", "source": "#41:0", - "sourceId": "ENST00000680887", + "sourceId": "enst00000680887", "sourceIdVersion": null }, "method": "POST", @@ -2023,12 +2170,36 @@ "createdAt": 1653589830294, "updatedAt": 1653589830294, "name": "enst00000680887", - "displayName": "enst00000680887", + "displayName": "ENST00000680887", "@rid": "#140:4", "@version": 1 } } }, + { + "opt": { + "body": { + "in": "#139:0", + "out": "#140:4", + "source": "#41:0" + }, + "method": "POST", + "uri": "/elementof" + }, + "result": { + "result": { + "@class": "ElementOf", + "out": "#140:4", + "in": "#139:0", + "source": "#41:0", + "createdBy": "#32:0", + "uuid": "d37fe3df-c75b-4b44-9ab5-53518fa7c292", + "createdAt": 1653589830322, + "@rid": "#95:1", + "@version": 1 + } + } + }, { "opt": { "body": { @@ -2056,8 +2227,8 @@ { "opt": { "body": { - "in": "#139:0", - "out": "#140:4", + "in": "#138:0", + "out": "#139:4", "source": "#41:0" }, "method": "POST", @@ -2066,13 +2237,44 @@ "result": { "result": { "@class": "ElementOf", - "out": "#140:4", - "in": "#139:0", + "out": "#139:4", + "in": "#138:0", "source": "#41:0", "createdBy": "#32:0", - "uuid": "d37fe3df-c75b-4b44-9ab5-53518fa7c292", - "createdAt": 1653589830322, - "@rid": "#95:1", + "uuid": "30897f3d-cc0e-4cf2-bd81-4d6b3103c0ac", + "createdAt": 1653589830331, + "@rid": "#96:1", + "@version": 1 + } + } + },{ + "opt": { + "body": { + "biotype": "protein", + "source": "#41:0", + "sourceId": "ensp00000505508", + "sourceIdVersion": "1" + }, + "method": "POST", + "uri": "/features" + }, + "result": { + "result": { + "@class": "Feature", + "deprecated": false, + "alias": false, + "biotype": "protein", + "source": "#41:0", + "sourceId": "ensp00000505508", + "sourceIdVersion": "1", + "createdBy": "#32:0", + "updatedBy": "#32:0", + "uuid": "70adf241-3fac-418b-aa84-0743b2a7f0c4", + "createdAt": 1653589830282, + "updatedAt": 1653589830282, + "name": "ensp00000505508", + "displayName": "ENSP00000505508.1", + "@rid": "#151:4", "@version": 1 } } @@ -2080,8 +2282,40 @@ { "opt": { "body": { - "in": "#138:0", - "out": "#139:4", + "biotype": "protein", + "source": "#41:0", + "sourceId": "ensp00000505508", + "sourceIdVersion": null + }, + "method": "POST", + "uri": "/features" + }, + "result": { + "result": { + "@class": "Feature", + "deprecated": false, + "alias": false, + "biotype": "protein", + "source": "#41:0", + "sourceId": "ensp00000505508", + "sourceIdVersion": null, + "createdBy": "#32:0", + "updatedBy": "#32:0", + "uuid": "8f8df45f-da32-4173-8f7d-f31b8ca0708a", + "createdAt": 1653589830294, + "updatedAt": 1653589830294, + "name": "ensp00000505508", + "displayName": "ENSP00000505508", + "@rid": "#152:4", + "@version": 1 + } + } + }, + { + "opt": { + "body": { + "in": "#140:4", + "out": "#152:4", "source": "#41:0" }, "method": "POST", @@ -2090,13 +2324,61 @@ "result": { "result": { "@class": "ElementOf", - "out": "#139:4", - "in": "#138:0", + "out": "#152:4", + "in": "#140:4", "source": "#41:0", "createdBy": "#32:0", "uuid": "30897f3d-cc0e-4cf2-bd81-4d6b3103c0ac", "createdAt": 1653589830331, - "@rid": "#96:1", + "@rid": "#73:1", + "@version": 1 + } + } + }, + { + "opt": { + "body": { + "in": "#151:4", + "out": "#152:4", + "source": "#41:0" + }, + "method": "POST", + "uri": "/generalizationof" + }, + "result": { + "result": { + "@class": "GeneralizationOf", + "out": "#152:4", + "in": "#151:4", + "source": "#41:0", + "createdBy": "#32:0", + "uuid": "309429ab-da47-4db6-a999-5f1014350e22", + "createdAt": 1653589830309, + "@rid": "#60:1", + "@version": 1 + } + } + }, + { + "opt": { + "body": { + "in": "#139:4", + "out": "#151:4", + "source": "#41:0" + }, + "method": "POST", + "uri": "/elementof" + }, + "result": { + "result": { + "@class": "ElementOf", + "out": "#151:4", + "in": "#139:4", + "source": "#41:0", + "createdBy": "#32:0", + "uuid": "d37fe3df-c75b-4b44-9ab5-53518fa7c292", + "createdAt": 1653589830322, + "@rid": "#72:1", "@version": 1 } } diff --git a/test/ensembl/ensembl.uploadFile.test.js b/test/ensembl/ensembl.uploadFile.test.js index bdde7f74..15d43aee 100644 --- a/test/ensembl/ensembl.uploadFile.test.js +++ b/test/ensembl/ensembl.uploadFile.test.js @@ -44,7 +44,7 @@ describe('uploadFile in Ensembl loader', () => { '@rid': '#138:0', edges: [], feature: { - biotype: 'gene', source: 'ensembl', sourceId: 'ENSG00000139618', sourceIdVersion: '17', + biotype: 'gene', source: 'ensembl', sourceId: 'ensg00000139618', sourceIdVersion: '17', }, }, { @@ -54,7 +54,7 @@ describe('uploadFile in Ensembl loader', () => { { class: 'generalizationof', in: '#138:0' }, ], feature: { - biotype: 'gene', source: 'ensembl', sourceId: 'ENSG00000139618', sourceIdVersion: null, + biotype: 'gene', source: 'ensembl', sourceId: 'ensg00000139618', sourceIdVersion: null, }, }, { @@ -63,7 +63,7 @@ describe('uploadFile in Ensembl loader', () => { { class: 'elementof', in: '#138:0' }, ], feature: { - biotype: 'transcript', source: 'ensembl', sourceId: 'ENST00000544455', sourceIdVersion: '6', + biotype: 'transcript', source: 'ensembl', sourceId: 'enst00000544455', sourceIdVersion: '6', }, }, { @@ -73,7 +73,7 @@ describe('uploadFile in Ensembl loader', () => { { class: 'generalizationof', in: '#140:0' }, ], feature: { - biotype: 'transcript', source: 'ensembl', sourceId: 'ENST00000544455', sourceIdVersion: null, + biotype: 'transcript', source: 'ensembl', sourceId: 'enst00000544455', sourceIdVersion: null, }, }, { @@ -82,7 +82,7 @@ describe('uploadFile in Ensembl loader', () => { { class: 'elementof', in: '#138:0' }, ], feature: { - biotype: 'transcript', source: 'ensembl', sourceId: 'ENST00000530893', sourceIdVersion: '6', + biotype: 'transcript', source: 'ensembl', sourceId: 'enst00000530893', sourceIdVersion: '6', }, }, { @@ -92,7 +92,7 @@ describe('uploadFile in Ensembl loader', () => { { class: 'generalizationof', in: '#139:3' }, ], feature: { - biotype: 'transcript', source: 'ensembl', sourceId: 'ENST00000530893', sourceIdVersion: null, + biotype: 'transcript', source: 'ensembl', sourceId: 'enst00000530893', sourceIdVersion: null, }, }, { @@ -101,18 +101,17 @@ describe('uploadFile in Ensembl loader', () => { { class: 'elementof', in: '#138:0' }, ], feature: { - biotype: 'transcript', source: 'ensembl', sourceId: 'ENST00000380152', sourceIdVersion: '8', + biotype: 'transcript', source: 'ensembl', sourceId: 'enst00000380152', sourceIdVersion: '8', }, }, { '@rid': '#138:4', edges: [ - { class: 'crossreferenceof', in: '#137:0' }, { class: 'elementof', in: '#139:0' }, { class: 'generalizationof', in: '#137:4' }, ], feature: { - biotype: 'transcript', source: 'ensembl', sourceId: 'ENST00000380152', sourceIdVersion: null, + biotype: 'transcript', source: 'ensembl', sourceId: 'enst00000380152', sourceIdVersion: null, }, }, { @@ -121,7 +120,7 @@ describe('uploadFile in Ensembl loader', () => { { class: 'elementof', in: '#138:0' }, ], feature: { - biotype: 'transcript', source: 'ensembl', sourceId: 'ENST00000680887', sourceIdVersion: '1', + biotype: 'transcript', source: 'ensembl', sourceId: 'enst00000680887', sourceIdVersion: '1', }, }, { @@ -131,7 +130,83 @@ describe('uploadFile in Ensembl loader', () => { { class: 'generalizationof', in: '#139:4' }, ], feature: { - biotype: 'transcript', source: 'ensembl', sourceId: 'ENST00000680887', sourceIdVersion: null, + biotype: 'transcript', source: 'ensembl', sourceId: 'enst00000680887', sourceIdVersion: null, + }, + }, + { + '@rid': '#145:1', + edges: [ + { class: 'elementof', in: '#140:0' }, + ], + feature: { + biotype: 'protein', source: 'ensembl', sourceId: 'ensp00000439902', sourceIdVersion: '1', + }, + }, + { + '@rid': '#146:1', + edges: [ + { class: 'elementof', in: '#137:1' }, + { class: 'generalizationof', in: '#145:1' }, + ], + feature: { + biotype: 'protein', source: 'ensembl', sourceId: 'ensp00000439902', sourceIdVersion: null, + }, + }, + { + '@rid': '#147:2', + edges: [ + { class: 'elementof', in: '#139:3' }, + ], + feature: { + biotype: 'protein', source: 'ensembl', sourceId: 'ensp00000499438', sourceIdVersion: '2', + }, + }, + { + '@rid': '#148:2', + edges: [ + { class: 'elementof', in: '#140:3' }, + { class: 'generalizationof', in: '#147:2' }, + ], + feature: { + biotype: 'protein', source: 'ensembl', sourceId: 'ensp00000499438', sourceIdVersion: null, + }, + }, + { + '@rid': '#149:3', + edges: [ + { class: 'elementof', in: '#137:4' }, + ], + feature: { + biotype: 'protein', source: 'ensembl', sourceId: 'ensp00000369497', sourceIdVersion: '3', + }, + }, + { + '@rid': '#150:3', + edges: [ + { class: 'elementof', in: '#138:4' }, + { class: 'generalizationof', in: '#149:3' }, + ], + feature: { + biotype: 'protein', source: 'ensembl', sourceId: 'ensp00000369497', sourceIdVersion: null, + }, + }, + { + '@rid': '#151:4', + edges: [ + { class: 'elementof', in: '#139:4' }, + ], + feature: { + biotype: 'protein', source: 'ensembl', sourceId: 'ensp00000505508', sourceIdVersion: '1', + }, + }, + { + '@rid': '#152:4', + edges: [ + { class: 'elementof', in: '#140:4' }, + { class: 'generalizationof', in: '#151:4' }, + ], + feature: { + biotype: 'protein', source: 'ensembl', sourceId: 'ensp00000505508', sourceIdVersion: null, }, }, { @@ -197,14 +272,6 @@ describe('uploadFile in Ensembl loader', () => { ], feature: { biotype: 'gene', name: 'XRCC11', source: 'hgnc' }, }, - { - '@rid': '#137:0', - edges: [], - feature: { - biotype: 'transcript', source: 'refseq', sourceId: 'NM_000059', sourceIdVersion: null, - }, - - }, ].map(el => Object.assign(el, { // Custom method - Returns the source's RID from mockDataset @@ -220,6 +287,7 @@ describe('uploadFile in Ensembl loader', () => { })); + // TEST SUITE // Sources const sources = [...new Set(expected.map((el) => (el.feature.source)))]; diff --git a/test/loadrecords.test.js b/test/loadrecords.test.js index ce7f1228..f7f831bf 100644 --- a/test/loadrecords.test.js +++ b/test/loadrecords.test.js @@ -1,8 +1,6 @@ const fs = require('fs'); const path = require('path'); -const { parseXmlToJson } = require('../src/util'); - const ctg = require('../src/clinicaltrialsgov'); const gene = require('../src/entrez/gene'); const pubmed = require('../src/entrez/pubmed'); @@ -34,12 +32,12 @@ afterEach(() => { describe('clinicaltrialsgov', () => { test('convertAPIRecord', async () => { - const raw = await parseXmlToJson(dataFileLoad('data/clinicaltrialsgov.NCT03478891.xml')); + const raw = await dataFileToJson('data/clinicaltrialsgov.NCT03478891.json'); const result = ctg.convertAPIRecord(raw); expect(result).toHaveProperty('sourceId', 'NCT03478891'); - expect(result).toHaveProperty('sourceIdVersion', '2019-07-15'); + expect(result).toHaveProperty('sourceIdVersion', '2020-10-26'); - expect(result).toHaveProperty('phases', ['Phase 1']); + expect(result).toHaveProperty('phases', ['PHASE1']); expect(result).toHaveProperty('startDate', '2018-05-16'); expect(result).toHaveProperty('completionDate', '2019-03-20'); expect(result).toHaveProperty('locations', [{ city: 'bethesda', country: 'united states' }]); @@ -49,11 +47,11 @@ describe('clinicaltrialsgov', () => { test('fetchAndLoadById', async () => { api.getUniqueRecordBy.mockRejectedValueOnce(new Error('doesnt exist yet')); - util.requestWithRetry.mockResolvedValueOnce(dataFileLoad('data/clinicaltrialsgov.NCT03478891.xml')); + util.requestWithRetry.mockResolvedValueOnce(dataFileToJson('data/clinicaltrialsgov.NCT03478891.json')); const result = await ctg.fetchAndLoadById(api, 'NCT03478891'); expect(result).toHaveProperty('sourceId', 'NCT03478891'); - expect(result).toHaveProperty('sourceIdVersion', '2019-07-15'); + expect(result).toHaveProperty('sourceIdVersion', '2020-10-26'); expect(result).toHaveProperty('source'); expect(result).toHaveProperty('phase', '1');