@sjcrh/proteinpaint-server 2.26.1 → 2.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cards/databrowser.json +1 -1
- package/cards/disco.json +69 -0
- package/cards/hic.json +1 -0
- package/cards/index.json +12 -2
- package/genome/hg19.js +0 -6
- package/genome/hg38.js +0 -6
- package/package.json +13 -10
- package/routes/README.md +84 -0
- package/routes/burden.ts +140 -0
- package/routes/gdc.maf.ts +106 -0
- package/routes/gdc.topMutatedGenes.ts +130 -0
- package/routes/gdc.topVariablyExpressedGenes.ts +97 -0
- package/routes/genelookup.ts +4 -12
- package/routes/healthcheck.ts +3 -46
- package/routes/termdb.violin.ts +78 -0
- package/server.js +1 -1
- package/utils/burden.R +334 -0
- package/utils/fastclust.R +8 -1
package/cards/databrowser.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"intro": "
|
|
2
|
+
"intro": "By uploading a dictionary in the Data Browser, one can explore the structure of their custom data. <br><br>Options to upload sample annotation and other data types are in development.",
|
|
3
3
|
"ppcalls":[
|
|
4
4
|
{
|
|
5
5
|
"isUi": true,
|
package/cards/disco.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ribbonMessage": "We're excited to announce our newest app, the <b>Disco Plot!</b>",
|
|
3
|
+
"ppcalls": [
|
|
4
|
+
{
|
|
5
|
+
"isUi": true,
|
|
6
|
+
"runargs": {
|
|
7
|
+
"noheader": true,
|
|
8
|
+
"nobox": 1,
|
|
9
|
+
"parseurl": false,
|
|
10
|
+
"tkui": "disco"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"message": "In this plot, all chromosomes of the genome are arranged in a circle. Multiple rings along the circle indicates example data points of mutation, copy number change, and structural variation/fusion events. Gene labels are based on mutation and sv/fusion events.",
|
|
15
|
+
"runargs": {
|
|
16
|
+
"noheader": true,
|
|
17
|
+
"nobox": true,
|
|
18
|
+
"genome": "hg38",
|
|
19
|
+
"disco": {
|
|
20
|
+
"mlst": [
|
|
21
|
+
{
|
|
22
|
+
"alt": "T",
|
|
23
|
+
"chr": "chr1",
|
|
24
|
+
"class": "M",
|
|
25
|
+
"dt": 1,
|
|
26
|
+
"gene": "H3F3A",
|
|
27
|
+
"isoform": "NM_002107",
|
|
28
|
+
"mname": "K28M",
|
|
29
|
+
"position": 226252135,
|
|
30
|
+
"ref": "A"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"dt": 4,
|
|
34
|
+
"chr": "chr1",
|
|
35
|
+
"start": 1,
|
|
36
|
+
"stop": 100000000,
|
|
37
|
+
"value": 0.5
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"dt": 4,
|
|
41
|
+
"chr": "chr1",
|
|
42
|
+
"start": 100000000,
|
|
43
|
+
"stop": 200000000,
|
|
44
|
+
"value": -0.5
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"chrA": "chr6",
|
|
48
|
+
"posA": 3067605,
|
|
49
|
+
"geneA": "MDC1",
|
|
50
|
+
"chrB": "chr12",
|
|
51
|
+
"posB": 61521661,
|
|
52
|
+
"geneB": "KMT2D",
|
|
53
|
+
"dt": 2,
|
|
54
|
+
"strandA": "+",
|
|
55
|
+
"strandB": "-"
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"testSpec": {
|
|
61
|
+
"button": 1,
|
|
62
|
+
"expected": {
|
|
63
|
+
"svg": 1,
|
|
64
|
+
"g": 2
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
}
|
package/cards/hic.json
CHANGED
package/cards/index.json
CHANGED
|
@@ -106,6 +106,7 @@
|
|
|
106
106
|
"section": "tracks",
|
|
107
107
|
"description": "Chromatin interaction at a locus",
|
|
108
108
|
"image": "https://proteinpaint.stjude.org/ppdemo/images/hic-square.png",
|
|
109
|
+
"ribbon": { "text": "updated", "expireDate": "2023-10-31" },
|
|
109
110
|
"sandboxJson": "hic",
|
|
110
111
|
"searchterms": ["hic", "hicstraw", "chromatin interaction", "conformation"]
|
|
111
112
|
},
|
|
@@ -285,7 +286,6 @@
|
|
|
285
286
|
"section": "apps",
|
|
286
287
|
"description": "2D scatter plot of samples & metadata",
|
|
287
288
|
"image": "https://proteinpaint.stjude.org/ppdemo/images/scatterplot-square.png",
|
|
288
|
-
"ribbon": { "text": "updated", "expireDate": "2023-03-01" },
|
|
289
289
|
"sandboxJson": "scatterplot",
|
|
290
290
|
"searchterms": ["tSNE", "lasso"]
|
|
291
291
|
},
|
|
@@ -307,6 +307,16 @@
|
|
|
307
307
|
"ribbon": {"text":"beta"},
|
|
308
308
|
"sandboxJson": "databrowser",
|
|
309
309
|
"searchterms": ["clinical", "dictionary"]
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
"type": "card",
|
|
313
|
+
"name": "Disco Plot",
|
|
314
|
+
"section": "apps",
|
|
315
|
+
"description": "Circos-like plot of genome-wide mutational events",
|
|
316
|
+
"image": "https://proteinpaint.stjude.org/ppdemo/images/disco-square.png",
|
|
317
|
+
"ribbon": { "text":"new", "expireDate": "2023-10-31" },
|
|
318
|
+
"sandboxJson": "disco",
|
|
319
|
+
"searchterms": ["disco"]
|
|
310
320
|
}
|
|
311
321
|
]
|
|
312
|
-
}
|
|
322
|
+
}
|
package/genome/hg19.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
var clinvar_1 = require("../dataset/clinvar");
|
|
4
3
|
exports.default = {
|
|
5
4
|
species: 'human',
|
|
6
5
|
genomefile: 'genomes/hg19.gz',
|
|
@@ -22,11 +21,6 @@ exports.default = {
|
|
|
22
21
|
db: 'utils/meme/motif_databases/HUMAN/HOCOMOCOv11_full_HUMAN_mono_meme_format.meme',
|
|
23
22
|
annotationfile: 'utils/meme/motif_databases/HUMAN/HOCOMOCOv11_full_annotation_HUMAN_mono.tsv'
|
|
24
23
|
},
|
|
25
|
-
clinvarVCF: {
|
|
26
|
-
file: 'hg19/clinvar.hg19.hgvs_short.vep.bcf.gz',
|
|
27
|
-
infokey: 'CLNSIG',
|
|
28
|
-
categories: clinvar_1.clinsig
|
|
29
|
-
},
|
|
30
24
|
tracks: [
|
|
31
25
|
{
|
|
32
26
|
__isgene: true,
|
package/genome/hg38.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
var clinvar_1 = require("../dataset/clinvar");
|
|
4
3
|
var cgc_js_1 = require("./cgc.js");
|
|
5
4
|
exports.default = {
|
|
6
5
|
species: 'human',
|
|
@@ -30,11 +29,6 @@ exports.default = {
|
|
|
30
29
|
db: 'utils/meme/motif_databases/HUMAN/HOCOMOCOv11_full_HUMAN_mono_meme_format.meme',
|
|
31
30
|
annotationfile: 'utils/meme/motif_databases/HUMAN/HOCOMOCOv11_full_annotation_HUMAN_mono.tsv'
|
|
32
31
|
},
|
|
33
|
-
clinvarVCF: {
|
|
34
|
-
file: 'hg38/clinvar.hg38.hgvs_short.vep.bcf.gz',
|
|
35
|
-
infokey: 'CLNSIG',
|
|
36
|
-
categories: clinvar_1.clinsig
|
|
37
|
-
},
|
|
38
32
|
tracks: [
|
|
39
33
|
{
|
|
40
34
|
__isgene: true,
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjcrh/proteinpaint-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.27.1",
|
|
4
4
|
"description": "a genomics visualization tool for exploring a cohort's genotype and phenotype data",
|
|
5
5
|
"main": "server.js",
|
|
6
6
|
"bin": "start.js",
|
|
7
7
|
"imports": {
|
|
8
|
-
"#shared/*": "./shared
|
|
8
|
+
"#shared/*": "./shared/*",
|
|
9
|
+
"#shared/types/*": "./shared/types/*",
|
|
9
10
|
"#src/*": "./src/*",
|
|
10
11
|
"#routes/*": "./routes/*"
|
|
11
12
|
},
|
|
@@ -15,17 +16,18 @@
|
|
|
15
16
|
"start": "tsc --esModuleInterop genome/*.ts dataset/*.ts && nodemon --enable-source-maps server.js --watch ./server*.js* --watch dataset/*.ts --watch genome/*.ts",
|
|
16
17
|
"pretest": "tsc --esModuleInterop genome/*.ts dataset/*.ts && ./test/pretest.js",
|
|
17
18
|
"test": "webpack --env NODE_ENV=test exportsFilename=all-test-context.js && node --enable-source-maps test/serverTests.js",
|
|
18
|
-
"
|
|
19
|
-
"
|
|
19
|
+
"precheckers": "tsc --esModuleInterop genome/*.ts dataset/*.ts",
|
|
20
|
+
"checkers": "webpack --config=./test/webpack.config.js && node test/emitPrepFiles.bundle.js && typia generate --input shared/checkers-raw --output shared/checkers",
|
|
21
|
+
"pretest:type": "npm run checkers",
|
|
22
|
+
"test:type": "webpack --env NODE_ENV=test exportsFilename=type-test-context.js && node --enable-source-maps test/serverTests.js",
|
|
23
|
+
"pretest:unit": "npm run precheckers",
|
|
24
|
+
"test:unit": "webpack --env NODE_ENV=test exportsFilename=unit-test-context.js && node --enable-source-maps test/serverTests.js",
|
|
20
25
|
"pretest:integration": "tsc --esModuleInterop genome/*.ts dataset/*.ts",
|
|
21
26
|
"test:integration": "echo 'TODO: server integration tests'",
|
|
22
27
|
"prepack": "tsc --esModuleInterop genome/*.ts dataset/*.ts && webpack --env NODE_ENV=production",
|
|
23
28
|
"response": "nodemon modules/test/test.server.js --watch src",
|
|
24
29
|
"getconf": "../build/getConfigProp.js",
|
|
25
|
-
"
|
|
26
|
-
"doc": "typedoc --json ../public/docs/server.json",
|
|
27
|
-
"precheckers": "npx ts-node ./augen/cli.js typeCheckers $PWD/routes ../../../routes > shared/checkers/raw/index.ts",
|
|
28
|
-
"checkers": "typia generate --input shared/checkers/raw --output shared/checkers/transformed --project ./shared/checkers/tsconfig.json"
|
|
30
|
+
"doc": "../augen/build.sh routes shared/types/routes shared/checkers ../public/docs/server"
|
|
29
31
|
},
|
|
30
32
|
"author": "",
|
|
31
33
|
"license": "SEE LICENSE IN ./LICENSE",
|
|
@@ -45,7 +47,6 @@
|
|
|
45
47
|
"ts-node": "^10.9.1",
|
|
46
48
|
"ts-patch": "^3.0.2",
|
|
47
49
|
"typedoc": "^0.24.8",
|
|
48
|
-
"typedoc-plugin-replace-text": "^3.1.0",
|
|
49
50
|
"typescript": "^5.0.3",
|
|
50
51
|
"typia": "^4.1.14",
|
|
51
52
|
"webpack": "^5.76.0",
|
|
@@ -54,7 +55,8 @@
|
|
|
54
55
|
"webpack-notifier": "^1.15.0"
|
|
55
56
|
},
|
|
56
57
|
"dependencies": {
|
|
57
|
-
"@sjcrh/
|
|
58
|
+
"@sjcrh/augen": "2.27.0",
|
|
59
|
+
"@sjcrh/proteinpaint-rust": "2.27.0",
|
|
58
60
|
"better-sqlite3": "^7.5.3",
|
|
59
61
|
"body-parser": "^1.15.2",
|
|
60
62
|
"canvas": "~2.9.3",
|
|
@@ -69,6 +71,7 @@
|
|
|
69
71
|
"jsonwebtoken": "^9.0.0",
|
|
70
72
|
"jstat": "^1.9.3",
|
|
71
73
|
"lazy": "^1.0.11",
|
|
74
|
+
"micromatch": "^4.0.5",
|
|
72
75
|
"minimatch": "^3.1.2",
|
|
73
76
|
"node-fetch": "^2.6.1",
|
|
74
77
|
"partjson": "^0.58.1",
|
package/routes/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Server Routes
|
|
2
|
+
|
|
3
|
+
## Introduction
|
|
4
|
+
|
|
5
|
+
This directory contains files that specify server route APIs. By following this guidelines,
|
|
6
|
+
the auto-generation of server routes, tests, and API documentation will work as expected.
|
|
7
|
+
|
|
8
|
+
## Guidelines
|
|
9
|
+
|
|
10
|
+
### 1. Use Express to do most of the routing logic
|
|
11
|
+
|
|
12
|
+
- decentralize the route handling code into smaller, independent handler functions
|
|
13
|
+
- common request processing logic, like genome, dataset, termdb set-up should be imported
|
|
14
|
+
from a shared helper module that is common to a group of routes, or for more advanced cases,
|
|
15
|
+
moved to a [router-level middleware](https://expressjs.com/en/guide/using-middleware.html#middleware.router)
|
|
16
|
+
|
|
17
|
+
### 2. Export an `api` from the route file
|
|
18
|
+
|
|
19
|
+
Use the code from other files in this directory as examples
|
|
20
|
+
|
|
21
|
+
TODO: define the `api` type
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
// work-in-progress
|
|
25
|
+
type RouteApi {
|
|
26
|
+
[key as methods]: RouteApiMethod
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type methods = 'get' | 'post'
|
|
30
|
+
|
|
31
|
+
type initArg = {
|
|
32
|
+
app?: any // Express app instance
|
|
33
|
+
genome: any // `Genome` from shared/types/genome.ts
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
@param
|
|
38
|
+
*/
|
|
39
|
+
type RouteApiMethod = {
|
|
40
|
+
endpoint: string
|
|
41
|
+
init: (initArg) => void
|
|
42
|
+
request: {
|
|
43
|
+
typeId: string
|
|
44
|
+
body?: any // specific to the route
|
|
45
|
+
}
|
|
46
|
+
response: {
|
|
47
|
+
typeId: string
|
|
48
|
+
header?: {
|
|
49
|
+
status: number
|
|
50
|
+
}
|
|
51
|
+
body?: any // specific to the route
|
|
52
|
+
}
|
|
53
|
+
examples: RouteExample[]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
type RouteExample = {
|
|
57
|
+
request: {
|
|
58
|
+
body?: any
|
|
59
|
+
}
|
|
60
|
+
response?: {
|
|
61
|
+
header: {
|
|
62
|
+
status: number
|
|
63
|
+
}
|
|
64
|
+
body?: any
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
### 3. Use the appropriate [HTTP response code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status)
|
|
69
|
+
|
|
70
|
+
This is a best practice especially for error responses. Use `res.status(code)` to set the error code.
|
|
71
|
+
This convention helps with error troubleshooting. Examples:
|
|
72
|
+
- Status `400` 'Bad Request', something is wrong with the http request payload
|
|
73
|
+
- Status `401` 'Unauthorized', the user must authenticate. The `server/src/auth.js` sets this status code
|
|
74
|
+
- Status `403` 'Forbidden', the user is authenticated/signed-in, but is not permitted to access the requested data
|
|
75
|
+
- Status `404` 'Not Found' for genome, dataset, or other data that is not found
|
|
76
|
+
- Status `500` 'Server Error' for errors related to the server process or host machine, such as the GDC API
|
|
77
|
+
not being available. Do not use code=`500` for errors that are related to specific request handler or data processing functions.
|
|
78
|
+
|
|
79
|
+
### 4. Auto-generate
|
|
80
|
+
|
|
81
|
+
- the server code will detect the routes in `server/src/run.sh`
|
|
82
|
+
- `npm run doc` to see the documented routes in http://localhost:3000/server.html
|
|
83
|
+
- `./augen/readme.sh > public/docs/readme.json` for content in http://localhost:3000/readme.html
|
|
84
|
+
|
package/routes/burden.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { BurdenRequest, BurdenResponse } from '#shared/types/routes/burden.ts'
|
|
2
|
+
import lines2R from '#src/lines2R.js'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import serverconfig from '#src/serverconfig.js'
|
|
5
|
+
import { write_file } from '#src/utils.js'
|
|
6
|
+
|
|
7
|
+
export const api = {
|
|
8
|
+
endpoint: 'burden',
|
|
9
|
+
methods: {
|
|
10
|
+
get: {
|
|
11
|
+
init({ genomes }) {
|
|
12
|
+
return async (req: any, res: any): Promise<void> => {
|
|
13
|
+
try {
|
|
14
|
+
const genome = genomes[req.query.genome]
|
|
15
|
+
if (!genome) throw `invalid q.genome=${req.query.genome}`
|
|
16
|
+
const q = req.query as BurdenRequest
|
|
17
|
+
const ds = genome.datasets[q.dslabel]
|
|
18
|
+
if (!ds) throw `invalid q.genome=${req.query.dslabel}`
|
|
19
|
+
if (!ds.cohort.cumburden?.files) throw `missing ds.cohort.cumburden.files`
|
|
20
|
+
|
|
21
|
+
const estimates = await getBurdenEstimates(req, ds)
|
|
22
|
+
const { keys, rows } = formatPayload(estimates)
|
|
23
|
+
res.send({ status: 'ok', keys, rows } as BurdenResponse)
|
|
24
|
+
} catch (e: any) {
|
|
25
|
+
res.send({ status: 'error', error: e.message || e })
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
request: {
|
|
30
|
+
typeId: 'BurdenRequest'
|
|
31
|
+
},
|
|
32
|
+
response: {
|
|
33
|
+
typeId: 'BurdenResponse'
|
|
34
|
+
},
|
|
35
|
+
examples: [
|
|
36
|
+
{
|
|
37
|
+
request: {
|
|
38
|
+
body: {
|
|
39
|
+
genome: 'hg38',
|
|
40
|
+
// TODO: !!! use hg38-test and TermdbTest !!!
|
|
41
|
+
dslabel: 'SJLife',
|
|
42
|
+
diaggrp: 5,
|
|
43
|
+
sex: 1,
|
|
44
|
+
white: 1,
|
|
45
|
+
agedx: 1,
|
|
46
|
+
bleo: 0,
|
|
47
|
+
etop: 0,
|
|
48
|
+
cisp: 0,
|
|
49
|
+
carbo: 0,
|
|
50
|
+
steriod: 0,
|
|
51
|
+
vcr: 0,
|
|
52
|
+
hdmtx: 0,
|
|
53
|
+
itmt: 0,
|
|
54
|
+
ced: 0,
|
|
55
|
+
dox: 0,
|
|
56
|
+
heart: 0,
|
|
57
|
+
brain: 0,
|
|
58
|
+
abd: 0,
|
|
59
|
+
pelvis: 0,
|
|
60
|
+
chest: 0
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
response: {
|
|
64
|
+
header: { status: 200 }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function getBurdenEstimates(q, ds) {
|
|
73
|
+
const infile = path.join(serverconfig.cachedir, Math.random().toString() + '.json')
|
|
74
|
+
for (const k in q.query) {
|
|
75
|
+
q.query[k] = Number(q.query[k])
|
|
76
|
+
}
|
|
77
|
+
const data = Object.assign({}, defaults, q.query)
|
|
78
|
+
//console.log(40, data, JSON.stringify(data))
|
|
79
|
+
await write_file(infile, JSON.stringify(data))
|
|
80
|
+
// TODO: use the dataset location
|
|
81
|
+
const { fit, surv, sample } = ds.cohort.cumburden.files
|
|
82
|
+
if (!fit || !surv || !sample) throw `missing one or more of ds.cohort.burden.files.{fit, surv, sample}`
|
|
83
|
+
const args = [
|
|
84
|
+
infile,
|
|
85
|
+
`${serverconfig.tpmasterdir}/${fit}`,
|
|
86
|
+
`${serverconfig.tpmasterdir}/${surv}`,
|
|
87
|
+
`${serverconfig.tpmasterdir}/${sample}`
|
|
88
|
+
]
|
|
89
|
+
const Routput = await lines2R(path.join(serverconfig.binpath, 'utils/burden.R'), [], args)
|
|
90
|
+
const estimates = JSON.parse(Routput[0])
|
|
91
|
+
return estimates
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function formatPayload(estimates) {
|
|
95
|
+
const rawKeys = Object.keys(estimates[0])
|
|
96
|
+
const outKeys = [] as string[]
|
|
97
|
+
const keys = [] as string[]
|
|
98
|
+
for (const k of rawKeys) {
|
|
99
|
+
if (k == 'chc') {
|
|
100
|
+
keys.push(k)
|
|
101
|
+
outKeys.push(k)
|
|
102
|
+
} else {
|
|
103
|
+
const age = Number(k.slice(1).split(',')[0])
|
|
104
|
+
if (age <= 60 && age % 2 == 0) {
|
|
105
|
+
keys.push(k)
|
|
106
|
+
outKeys.push(`burden${age}`)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const rows = [] as number[][]
|
|
111
|
+
// v = an array of objects with age as keys as cumulative burden as value for a given CHC
|
|
112
|
+
for (const v of estimates) {
|
|
113
|
+
rows.push(keys.map(k => v[k]))
|
|
114
|
+
}
|
|
115
|
+
return { keys: outKeys, rows }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const defaults = Object.freeze({
|
|
119
|
+
diaggrp: 5,
|
|
120
|
+
sex: 0,
|
|
121
|
+
white: 1,
|
|
122
|
+
agedx: 1,
|
|
123
|
+
// chemotherapy
|
|
124
|
+
steriod: 0,
|
|
125
|
+
bleo: 0,
|
|
126
|
+
vcr: 0, //12, // Vincristine
|
|
127
|
+
etop: 0, //2500, // Etoposide
|
|
128
|
+
itmt: 0, // Intrathecal methothrexate_grp: 0,
|
|
129
|
+
ced: 0, //1.6, // Cyclophosphamide, 0.7692 mean 7692.
|
|
130
|
+
cisp: 0, //300, // Cisplatin
|
|
131
|
+
dox: 0, // Anthracycline, 3 mean 300 ml/m2
|
|
132
|
+
carbo: 0, // Carboplatin
|
|
133
|
+
hdmtx: 0, // High-Dose Methotrexate
|
|
134
|
+
// radiation
|
|
135
|
+
brain: 0, //5.4,
|
|
136
|
+
chest: 0, //2.4,
|
|
137
|
+
heart: 0,
|
|
138
|
+
pelvis: 0,
|
|
139
|
+
abd: 0 //2.4
|
|
140
|
+
})
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { GdcMafResponse, File } from '#shared/types/routes/gdc.maf.ts'
|
|
2
|
+
import { fileSize } from '#shared/fileSize.js'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import got from 'got'
|
|
5
|
+
|
|
6
|
+
const apihost = process.env.PP_GDC_HOST || 'https://api.gdc.cancer.gov'
|
|
7
|
+
|
|
8
|
+
export const api = {
|
|
9
|
+
endpoint: 'gdc/maf',
|
|
10
|
+
methods: {
|
|
11
|
+
get: {
|
|
12
|
+
init({ genomes }) {
|
|
13
|
+
// genomes parameter is not used
|
|
14
|
+
// could be used later to verify hg38/GDC is on this instance and otherwise disable this route..
|
|
15
|
+
|
|
16
|
+
return async (req: any, res: any): Promise<void> => {
|
|
17
|
+
try {
|
|
18
|
+
const files = await listMafFiles(req)
|
|
19
|
+
const payload = { files } as GdcMafResponse
|
|
20
|
+
res.send(payload)
|
|
21
|
+
} catch (e: any) {
|
|
22
|
+
res.send({ status: 'error', error: e.message || e })
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
request: {
|
|
27
|
+
typeId: null
|
|
28
|
+
//valid: default to type checker
|
|
29
|
+
},
|
|
30
|
+
response: {
|
|
31
|
+
typeId: 'GdcMafResponse'
|
|
32
|
+
// will combine this with type checker
|
|
33
|
+
//valid: (t) => {}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/*
|
|
40
|
+
req.query {
|
|
41
|
+
filter0 // optional gdc GFF cohort filter, invisible and read only
|
|
42
|
+
}
|
|
43
|
+
*/
|
|
44
|
+
async function listMafFiles(req: any) {
|
|
45
|
+
const filters = {
|
|
46
|
+
op: 'and',
|
|
47
|
+
content: [
|
|
48
|
+
{
|
|
49
|
+
op: '=',
|
|
50
|
+
content: { field: 'data_format', value: 'MAF' }
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (req.query.filter0) {
|
|
56
|
+
filters.content.push(req.query.filter0)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const headers = { 'Content-Type': 'application/json', Accept: 'application/json' }
|
|
60
|
+
|
|
61
|
+
const data = {
|
|
62
|
+
filters,
|
|
63
|
+
size: 1000,
|
|
64
|
+
fields: [
|
|
65
|
+
'id',
|
|
66
|
+
'file_size',
|
|
67
|
+
'experimental_strategy',
|
|
68
|
+
'cases.submitter_id', // used when listing all cases & files
|
|
69
|
+
//'associated_entities.entity_submitter_id', // semi human readable
|
|
70
|
+
//'associated_entities.case_id', // case uuid
|
|
71
|
+
'cases.samples.sample_type',
|
|
72
|
+
'analysis.workflow_type' // to drop out those as skip_workflow_type
|
|
73
|
+
].join(',')
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const response = await got.post(path.join(apihost, 'files'), { headers, body: JSON.stringify(data) })
|
|
77
|
+
|
|
78
|
+
let re
|
|
79
|
+
try {
|
|
80
|
+
re = JSON.parse(response.body)
|
|
81
|
+
} catch (e) {
|
|
82
|
+
throw 'invalid JSON from ' + api.endpoint
|
|
83
|
+
}
|
|
84
|
+
if (!Array.isArray(re.data?.hits)) throw 're.data.hits[] not array'
|
|
85
|
+
|
|
86
|
+
// flatten api return to table row objects
|
|
87
|
+
// it is possible to set a max size limit to limit the number of files passed to client
|
|
88
|
+
const files = [] as File[]
|
|
89
|
+
for (const h of re.data.hits) {
|
|
90
|
+
const file = {
|
|
91
|
+
id: h.id,
|
|
92
|
+
workflow_type: h.analysis?.workflow_type,
|
|
93
|
+
experimental_strategy: h.experimental_strategy,
|
|
94
|
+
file_size: fileSize(h.file_size)
|
|
95
|
+
} as File
|
|
96
|
+
const c = h.cases?.[0]
|
|
97
|
+
if (c) {
|
|
98
|
+
file.case_submitter_id = c.submitter_id
|
|
99
|
+
if (c.samples) {
|
|
100
|
+
file.sample_types = c.samples.map(i => i.sample_type).join(', ')
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
files.push(file)
|
|
104
|
+
}
|
|
105
|
+
return files
|
|
106
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { GdcTopMutatedGeneResponse } from '#shared/types/routes/gdc.topMutatedGenes.ts'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import got from 'got'
|
|
4
|
+
|
|
5
|
+
const apihost = process.env.PP_GDC_HOST || 'https://api.gdc.cancer.gov'
|
|
6
|
+
|
|
7
|
+
export const api = {
|
|
8
|
+
endpoint: 'gdc/topMutatedGenes',
|
|
9
|
+
methods: {
|
|
10
|
+
get: {
|
|
11
|
+
init({ genomes }) {
|
|
12
|
+
/*
|
|
13
|
+
genomes parameter is currently not used
|
|
14
|
+
could be used later to:
|
|
15
|
+
- verify hg38/GDC is on this instance and otherwise disable this route..
|
|
16
|
+
- perform conversion on gene name/id for future on needs
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
return async (req: any, res: any): Promise<void> => {
|
|
20
|
+
try {
|
|
21
|
+
const genes = await getGenes(req.query)
|
|
22
|
+
const payload = { genes } as GdcTopMutatedGeneResponse
|
|
23
|
+
res.send(payload)
|
|
24
|
+
} catch (e: any) {
|
|
25
|
+
res.send({ status: 'error', error: e.message || e })
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
request: {
|
|
30
|
+
typeId: null
|
|
31
|
+
//valid: default to type checker
|
|
32
|
+
},
|
|
33
|
+
response: {
|
|
34
|
+
typeId: 'GdcTopMutatedGeneResponse'
|
|
35
|
+
// will combine this with type checker
|
|
36
|
+
//valid: (t) => {}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/*
|
|
43
|
+
req.query {
|
|
44
|
+
filter0 // optional gdc GFF cohort filter, invisible and read only
|
|
45
|
+
FIXME should there be pp filter too?
|
|
46
|
+
geneFilter?: str
|
|
47
|
+
maxGenes: int
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
mayAddCGC2filter() are copied to
|
|
51
|
+
/utils/gdc/topSSMgenes.js
|
|
52
|
+
and hosted on https://proteinpaint.stjude.org/GDC/
|
|
53
|
+
*/
|
|
54
|
+
async function getGenes(q: any) {
|
|
55
|
+
let _f = { op: 'and', content: [] } // allow blank filter to test geneset edit ui (without filter)
|
|
56
|
+
if (q.filter0) {
|
|
57
|
+
if (typeof q.filter0 != 'object') throw 'filter0 not object'
|
|
58
|
+
_f = q.filter0
|
|
59
|
+
}
|
|
60
|
+
const response = await got.post(path.join(apihost, '/analysis/top_mutated_genes_by_project'), {
|
|
61
|
+
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
|
62
|
+
body: JSON.stringify({
|
|
63
|
+
size: q.maxGenes || 50,
|
|
64
|
+
fields: 'symbol',
|
|
65
|
+
filters: mayAddCGC2filter(_f, q.geneFilter)
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
const re = JSON.parse(response.body)
|
|
69
|
+
const genes = [] as string[]
|
|
70
|
+
for (const hit of re.data.hits) {
|
|
71
|
+
if (!hit.symbol) continue
|
|
72
|
+
genes.push(hit.symbol)
|
|
73
|
+
}
|
|
74
|
+
return genes
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/*
|
|
78
|
+
str:
|
|
79
|
+
stringified gdc filter object, should not include the "genes.is_cancer_gene_census" filter element
|
|
80
|
+
geneFilter: str
|
|
81
|
+
if = 'CGC', insert following element into the filter and return stringified obj
|
|
82
|
+
|
|
83
|
+
{
|
|
84
|
+
"op":"and",
|
|
85
|
+
"content":[
|
|
86
|
+
{
|
|
87
|
+
"content":{ "field":"genes.is_cancer_gene_census", "value":["true"] },
|
|
88
|
+
"op":"in"
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
*/
|
|
93
|
+
function mayAddCGC2filter(f: any, geneFilter?: string) {
|
|
94
|
+
// reformulate f into f2
|
|
95
|
+
// f may be "in" or "and". f2 is always "and", in order to join in additional filters
|
|
96
|
+
let f2
|
|
97
|
+
|
|
98
|
+
if (f.op == 'in') {
|
|
99
|
+
// wrap f into f2
|
|
100
|
+
f2 = { op: 'and', content: [f] }
|
|
101
|
+
} else if (f.op == 'and') {
|
|
102
|
+
// no need to wrap
|
|
103
|
+
f2 = f
|
|
104
|
+
} else {
|
|
105
|
+
throw 'f.op not "in" or "and"'
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// per Phil on 12/16/2022, following filters ensure to return IDH1 as 1st gene for gliomas
|
|
109
|
+
f2.content.push({
|
|
110
|
+
op: 'NOT',
|
|
111
|
+
content: {
|
|
112
|
+
field: 'ssms.consequence.transcript.annotation.vep_impact',
|
|
113
|
+
value: 'missing'
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
f2.content.push({
|
|
117
|
+
op: 'in',
|
|
118
|
+
content: {
|
|
119
|
+
field: 'ssms.consequence.transcript.consequence_type',
|
|
120
|
+
value: ['missense_variant', 'frameshift_variant', 'start_lost', 'stop_lost', 'stop_gained']
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
if (geneFilter == 'CGC') {
|
|
125
|
+
// using CGC genes, add in filter
|
|
126
|
+
f2.content.push({ op: 'in', content: { field: 'genes.is_cancer_gene_census', value: ['true'] } })
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return f2
|
|
130
|
+
}
|