@sjcrh/proteinpaint-server 2.38.1 → 2.39.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/package.json +2 -2
- package/routes/gdc.maf.ts +17 -4
- package/routes/gdc.mafBuild.ts +12 -5
- package/routes/hicdata.ts +2 -2
- package/routes/termdb.categories.ts +8 -6
- package/routes/termdb.cluster.ts +176 -0
- package/routes/termdb.getdescrstats.ts +5 -43
- package/routes/termdb.getnumericcategories.ts +99 -0
- package/routes/termdb.getpercentile.ts +7 -6
- package/routes/termdb.getrootterm.ts +67 -0
- package/routes/termdb.gettermchildren.ts +82 -0
- package/routes/termdb.topVariablyExpressedGenes.ts +60 -11
- package/routes/termdb.violin.ts +3 -3
- package/server.js +1 -1
- package/server.js.map +1 -1
- package/src/mds3.gdc.filter.js +9 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjcrh/proteinpaint-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.39.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",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"@sjcrh/augen": "2.35.0",
|
|
60
|
-
"@sjcrh/proteinpaint-rust": "2.
|
|
60
|
+
"@sjcrh/proteinpaint-rust": "2.39.0",
|
|
61
61
|
"better-sqlite3": "^7.5.3",
|
|
62
62
|
"body-parser": "^1.15.2",
|
|
63
63
|
"canvas": "~2.9.3",
|
package/routes/gdc.maf.ts
CHANGED
|
@@ -3,15 +3,28 @@ import path from 'path'
|
|
|
3
3
|
import got from 'got'
|
|
4
4
|
import serverconfig from '#src/serverconfig.js'
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
/*
|
|
7
|
+
this route lists available gdc MAF files based on user's cohort filter
|
|
8
|
+
and return them to client to be shown in a table for selection
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const apihost = process.env.PP_GDC_HOST || 'https://api.gdc.cancer.gov' // to switch to serverconfig export
|
|
12
|
+
|
|
13
|
+
const maxFileNumber = 1000 // determines max number of files to return to client
|
|
14
|
+
// preliminary testing:
|
|
15
|
+
// 36s for 1000 (87Mb)
|
|
16
|
+
// 78s for 2000 (177Mb)
|
|
17
|
+
// if safe to increase to 2000, maybe fast when this runs in gdc env
|
|
18
|
+
|
|
8
19
|
const allowedWorkflowType = 'Aliquot Ensemble Somatic Variant Merging and Masking'
|
|
9
|
-
|
|
20
|
+
|
|
21
|
+
// change to 400 so it won't limit number of files; should keep this setting as a safeguard; also it's fast to check file size (.5s in gdc.mafBuild.ts)
|
|
22
|
+
export const maxTotalSizeCompressed = serverconfig.features.gdcMafMaxFileSize || 400000000 // 400Mb
|
|
10
23
|
|
|
11
24
|
export const api = {
|
|
12
25
|
endpoint: 'gdc/maf',
|
|
13
26
|
methods: {
|
|
14
|
-
|
|
27
|
+
all: {
|
|
15
28
|
init,
|
|
16
29
|
request: {
|
|
17
30
|
typeId: 'GdcMafRequest'
|
package/routes/gdc.mafBuild.ts
CHANGED
|
@@ -5,9 +5,9 @@ import { run_rust_stream } from '@sjcrh/proteinpaint-rust'
|
|
|
5
5
|
import serverconfig from '#src/serverconfig.js'
|
|
6
6
|
import Readable from 'stream'
|
|
7
7
|
import { GdcMafBuildRequest } from '#shared/types/routes/gdc.mafBuild.ts'
|
|
8
|
+
import { maxTotalSizeCompressed } from './gdc.maf.ts'
|
|
8
9
|
|
|
9
10
|
const apihost = process.env.PP_GDC_HOST || 'https://api.gdc.cancer.gov'
|
|
10
|
-
const maxTotalSizeCompressed = serverconfig.features.gdcMafMaxFileSize || 50000000 // 50Mb
|
|
11
11
|
|
|
12
12
|
export const api = {
|
|
13
13
|
endpoint: 'gdc/mafBuild',
|
|
@@ -43,10 +43,16 @@ async function buildMaf(q: GdcMafBuildRequest, res: any) {
|
|
|
43
43
|
const t0 = Date.now()
|
|
44
44
|
|
|
45
45
|
const fileLst2 = (await getFileLstUnderSizeLimit(q.fileIdLst)) as string[]
|
|
46
|
-
|
|
46
|
+
|
|
47
|
+
if (serverconfig.debugmode)
|
|
48
|
+
console.log(
|
|
49
|
+
`${fileLst2.length} out of ${q.fileIdLst.length} input MAF files accepted by size limit`,
|
|
50
|
+
Date.now() - t0
|
|
51
|
+
)
|
|
47
52
|
|
|
48
53
|
const arg = {
|
|
49
54
|
fileIdLst: fileLst2,
|
|
55
|
+
columns: q.columns,
|
|
50
56
|
host: path.join(apihost, 'data') // must use the /data/ endpoint from current host
|
|
51
57
|
}
|
|
52
58
|
|
|
@@ -55,11 +61,12 @@ async function buildMaf(q: GdcMafBuildRequest, res: any) {
|
|
|
55
61
|
res.setHeader('Content-Disposition', 'attachment; filename=cohort.maf.gz')
|
|
56
62
|
rustStream.pipe(res)
|
|
57
63
|
|
|
58
|
-
console.log('rust gdcmaf', Date.now() - t0)
|
|
59
|
-
|
|
60
64
|
rustStream.on('end', () => {
|
|
65
|
+
// report amount of time taken to run rust
|
|
66
|
+
if (serverconfig.debugmode) console.log('rust gdcmaf', Date.now() - t0)
|
|
61
67
|
res.end()
|
|
62
68
|
})
|
|
69
|
+
|
|
63
70
|
rustStream.on('error', err => {
|
|
64
71
|
console.error(err)
|
|
65
72
|
res.statusCode = 500
|
|
@@ -80,7 +87,7 @@ async function getFileLstUnderSizeLimit(lst: string[]) {
|
|
|
80
87
|
op: 'in',
|
|
81
88
|
content: { field: 'file_id', value: lst }
|
|
82
89
|
},
|
|
83
|
-
size:
|
|
90
|
+
size: 10000,
|
|
84
91
|
fields: 'file_size'
|
|
85
92
|
}
|
|
86
93
|
const headers = { 'Content-Type': 'application/json', Accept: 'application/json' }
|
package/routes/hicdata.ts
CHANGED
|
@@ -40,7 +40,7 @@ export const api: any = {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
function init(
|
|
43
|
+
function init() {
|
|
44
44
|
return async (req: any, res: any): Promise<void> => {
|
|
45
45
|
try {
|
|
46
46
|
const payload = await handle_hicdata(req.query as HicdataRequest)
|
|
@@ -60,7 +60,7 @@ function handle_hicdata(q: HicdataRequest) {
|
|
|
60
60
|
if (e) reject({ error: 'illegal file name' })
|
|
61
61
|
|
|
62
62
|
const par = [
|
|
63
|
-
|
|
63
|
+
q.matrixType || 'observed',
|
|
64
64
|
q.nmeth || 'NONE',
|
|
65
65
|
file,
|
|
66
66
|
q.pos1,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { getcategoriesRequest, getcategoriesResponse } from '#shared/types/routes/termdb.categories.ts'
|
|
2
2
|
import { getOrderedLabels } from '#src/termdb.barchart.js'
|
|
3
3
|
import { getData } from '#src/termdb.matrix.js'
|
|
4
4
|
|
|
@@ -68,7 +68,7 @@ export const api: any = {
|
|
|
68
68
|
|
|
69
69
|
function init({ genomes }) {
|
|
70
70
|
return async (req: any, res: any): Promise<void> => {
|
|
71
|
-
const q = req.query
|
|
71
|
+
const q = req.query as getcategoriesRequest
|
|
72
72
|
try {
|
|
73
73
|
const g = genomes[req.query.genome]
|
|
74
74
|
if (!g) throw 'invalid genome name'
|
|
@@ -88,8 +88,8 @@ function init({ genomes }) {
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
async function trigger_getcategories(
|
|
91
|
-
q: { tid: string | number; type: string; filter: any; term1_q: any; currentGeneNames
|
|
92
|
-
res: { send: (arg0: { lst: any[]; orderedLabels
|
|
91
|
+
q: { tid: string | number; type: string; filter: any; term1_q: any; currentGeneNames?: string[]; rglst?: any },
|
|
92
|
+
res: { send: (arg0: { lst: any[]; orderedLabels?: any }) => void },
|
|
93
93
|
tdb: { q: { termjsonByOneid: (arg0: any) => any } },
|
|
94
94
|
ds: { assayAvailability: { byDt: { [s: string]: any } | ArrayLike<any> } },
|
|
95
95
|
genome: any
|
|
@@ -99,13 +99,15 @@ async function trigger_getcategories(
|
|
|
99
99
|
if (!q.tid) throw '.tid missing'
|
|
100
100
|
const term =
|
|
101
101
|
q.type == 'geneVariant' ? { name: q.tid, type: 'geneVariant', isleaf: true } : tdb.q.termjsonByOneid(q.tid)
|
|
102
|
+
|
|
102
103
|
const arg = {
|
|
103
104
|
filter: q.filter,
|
|
104
105
|
terms:
|
|
105
106
|
q.type == 'geneVariant'
|
|
106
107
|
? [{ term: term, q: { isAtomic: true } }]
|
|
107
108
|
: [{ id: q.tid, term, q: q.term1_q || getDefaultQ(term, q) }],
|
|
108
|
-
currentGeneNames: q.currentGeneNames
|
|
109
|
+
currentGeneNames: q.currentGeneNames, // optional, from mds3 mayAddGetCategoryArgs()
|
|
110
|
+
rglst: q.rglst // optional, from mds3 mayAddGetCategoryArgs()
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
const data = await getData(arg, ds, genome)
|
|
@@ -200,7 +202,7 @@ async function trigger_getcategories(
|
|
|
200
202
|
res.send({
|
|
201
203
|
lst,
|
|
202
204
|
orderedLabels
|
|
203
|
-
})
|
|
205
|
+
} as getcategoriesResponse)
|
|
204
206
|
}
|
|
205
207
|
|
|
206
208
|
function getDefaultQ(
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { TermdbClusterRequest, TermdbClusterResponse } from '#shared/types/routes/termdb.cluster.ts'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import * as utils from '#src/utils.js'
|
|
4
|
+
import serverconfig from '#src/serverconfig.js'
|
|
5
|
+
import { GeneExpressionQuery, GeneExpressionQueryNative } from '#shared/types/dataset.ts'
|
|
6
|
+
import { gdc_validate_query_geneExpression } from '#src/mds3.gdc.js'
|
|
7
|
+
import { mayLimitSamples } from '#src/mds3.filter.js'
|
|
8
|
+
import { doClustering } from '#src/doClustering.js' // unable to convert this to ts yet, when converted, move all code here
|
|
9
|
+
import { dtgeneexpression } from '#shared/common.js'
|
|
10
|
+
|
|
11
|
+
export const api = {
|
|
12
|
+
endpoint: 'termdb/cluster',
|
|
13
|
+
methods: {
|
|
14
|
+
all: {
|
|
15
|
+
init,
|
|
16
|
+
request: {
|
|
17
|
+
typeId: 'TermdbClusterRequest'
|
|
18
|
+
},
|
|
19
|
+
response: {
|
|
20
|
+
typeId: 'TermdbClusterResponse'
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function init({ genomes }) {
|
|
27
|
+
return async (req: any, res: any): Promise<void> => {
|
|
28
|
+
const q = req.query as TermdbClusterRequest
|
|
29
|
+
let result
|
|
30
|
+
try {
|
|
31
|
+
const g = genomes[q.genome]
|
|
32
|
+
if (!g) throw 'invalid genome name'
|
|
33
|
+
const ds = g.datasets[q.dslabel]
|
|
34
|
+
if (!ds) throw 'invalid dataset name'
|
|
35
|
+
if (ds.__gdc && !ds.__gdc.doneCaching)
|
|
36
|
+
throw 'The server has not finished caching the case IDs: try again in ~2 minutes'
|
|
37
|
+
if (q.dataType == dtgeneexpression) {
|
|
38
|
+
if (!ds.queries?.geneExpression) throw 'no geneExpression data on this dataset'
|
|
39
|
+
result = (await getResult(q, ds)) as TermdbClusterResponse
|
|
40
|
+
} else {
|
|
41
|
+
throw 'unknown q.dataType ' + q.dataType
|
|
42
|
+
}
|
|
43
|
+
} catch (e: any) {
|
|
44
|
+
if (e.stack) console.log(e.stack)
|
|
45
|
+
result = {
|
|
46
|
+
status: e.status || 400,
|
|
47
|
+
error: e.message || e
|
|
48
|
+
} as TermdbClusterResponse
|
|
49
|
+
}
|
|
50
|
+
res.send(result)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function getResult(q: TermdbClusterRequest, ds: any) {
|
|
55
|
+
const { gene2sample2value, byTermId, bySampleId } = await ds.queries.geneExpression.get(q)
|
|
56
|
+
if (gene2sample2value.size == 0) throw 'no data'
|
|
57
|
+
if (gene2sample2value.size == 1) {
|
|
58
|
+
// get data for only 1 gene; still return data, may create violin plot later
|
|
59
|
+
const g = Array.from(gene2sample2value.keys())[0]
|
|
60
|
+
return { gene: g, data: gene2sample2value.get(g) }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// have data for multiple genes, run clustering
|
|
64
|
+
const t = Date.now() // use "t=new Date()" will lead to tsc error
|
|
65
|
+
const clustering = await doClustering(gene2sample2value, q, ds)
|
|
66
|
+
if (serverconfig.debugmode) console.log('clustering done:', Date.now() - t, 'ms')
|
|
67
|
+
return { clustering, byTermId, bySampleId }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function validate_query_geneExpression(ds: any, genome: any) {
|
|
71
|
+
const q = ds.queries.geneExpression as GeneExpressionQuery
|
|
72
|
+
if (!q) return
|
|
73
|
+
|
|
74
|
+
if (q.src == 'gdcapi') {
|
|
75
|
+
gdc_validate_query_geneExpression(ds, genome)
|
|
76
|
+
// q.get() added
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
if (q.src == 'native') {
|
|
80
|
+
validateNative(q, ds, genome)
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
throw 'unknown queries.geneExpression.src'
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function validateNative(q: GeneExpressionQueryNative, ds: any, genome: any) {
|
|
87
|
+
q.file = path.join(serverconfig.tpmasterdir, q.file)
|
|
88
|
+
await utils.validate_tabixfile(q.file)
|
|
89
|
+
q.nochr = await utils.tabix_is_nochr(q.file, null, genome)
|
|
90
|
+
q.samples = [] as number[]
|
|
91
|
+
|
|
92
|
+
{
|
|
93
|
+
// is a gene-by-sample matrix file
|
|
94
|
+
const lines = await utils.get_header_tabix(q.file)
|
|
95
|
+
if (!lines[0]) throw 'header line missing from ' + q.file
|
|
96
|
+
const l = lines[0].split('\t')
|
|
97
|
+
if (l.slice(0, 4).join('\t') != '#chr\tstart\tstop\tgene') throw 'header line has wrong content for columns 1-4'
|
|
98
|
+
for (let i = 4; i < l.length; i++) {
|
|
99
|
+
const id = ds.cohort.termdb.q.sampleName2id(l[i])
|
|
100
|
+
if (id == undefined) throw 'unknown sample from header'
|
|
101
|
+
q.samples.push(id)
|
|
102
|
+
}
|
|
103
|
+
console.log(q.samples.length, 'samples from geneExpression of', ds.label)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/*
|
|
107
|
+
query exp data one gene at a time
|
|
108
|
+
param{}
|
|
109
|
+
.genes[{}]
|
|
110
|
+
.gene=str
|
|
111
|
+
.chr=str
|
|
112
|
+
.start=int
|
|
113
|
+
.stop=int
|
|
114
|
+
.filterObj{}
|
|
115
|
+
*/
|
|
116
|
+
q.get = async (param: TermdbClusterRequest) => {
|
|
117
|
+
const limitSamples = await mayLimitSamples(param, q.samples, ds)
|
|
118
|
+
if (limitSamples?.size == 0) {
|
|
119
|
+
// got 0 sample after filtering, must still return expected structure with no data
|
|
120
|
+
return { gene2sample2value: new Set(), byTermId: {}, bySampleId: {} }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// has at least 1 sample passing filter and with exp data
|
|
124
|
+
// TODO what if there's just 1 sample not enough for clustering?
|
|
125
|
+
const bySampleId = {}
|
|
126
|
+
if (limitSamples) {
|
|
127
|
+
for (const sid of limitSamples) {
|
|
128
|
+
bySampleId[sid] = { label: ds.cohort.termdb.q.id2sampleName(sid) }
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
// use all samples with exp data
|
|
132
|
+
for (const sid of q.samples) {
|
|
133
|
+
bySampleId[sid] = { label: ds.cohort.termdb.q.id2sampleName(sid) }
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const gene2sample2value = new Map() // k: gene symbol, v: { sampleId : value }
|
|
138
|
+
|
|
139
|
+
for (const g of param.genes) {
|
|
140
|
+
// FIXME newly added geneVariant terms from client to be changed to {gene} but not {name}
|
|
141
|
+
if (!g.gene) continue
|
|
142
|
+
|
|
143
|
+
if (!g.chr) {
|
|
144
|
+
// quick fix: newly added gene from client will lack chr/start/stop
|
|
145
|
+
const lst = genome.genedb.getjsonbyname.all(g.gene)
|
|
146
|
+
if (lst.length == 0) continue
|
|
147
|
+
const j = JSON.parse(lst.find(i => i.isdefault).genemodel || lst[0].genemodel)
|
|
148
|
+
g.start = j.start
|
|
149
|
+
g.stop = j.stop
|
|
150
|
+
g.chr = j.chr
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
gene2sample2value.set(g.gene, {})
|
|
154
|
+
await utils.get_lines_bigfile({
|
|
155
|
+
args: [q.file, (q.nochr ? g.chr?.replace('chr', '') : g.chr) + ':' + g.start + '-' + g.stop], // must do g.chr?.replace to avoid tsc error
|
|
156
|
+
callback: line => {
|
|
157
|
+
const l = line.split('\t')
|
|
158
|
+
// case-insensitive match! FIXME if g.gene is alias won't work
|
|
159
|
+
if (l[3].toLowerCase() != g.gene.toLowerCase()) return
|
|
160
|
+
for (let i = 4; i < l.length; i++) {
|
|
161
|
+
const sampleId = q.samples[i - 4]
|
|
162
|
+
if (limitSamples && !limitSamples.has(sampleId)) continue // doing filtering and sample of current column is not used
|
|
163
|
+
// if l[i] is blank string?
|
|
164
|
+
const v = Number(l[i])
|
|
165
|
+
if (Number.isNaN(v)) throw 'exp value not number'
|
|
166
|
+
gene2sample2value.get(g.gene)[sampleId] = v
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
} as any)
|
|
170
|
+
// Above!! add "as any" to suppress a npx tsc alert
|
|
171
|
+
}
|
|
172
|
+
// pass blank byTermId to match with expected output structure
|
|
173
|
+
const byTermId = {}
|
|
174
|
+
return { gene2sample2value, byTermId, bySampleId }
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import * as termdbsql from '#src/termdb.sql.js'
|
|
1
|
+
import { getdescrstatsRequest, getdescrstatsResponse } from '#shared/types/routes/termdb.getdescrstats.ts'
|
|
2
|
+
import * as termdbsql from '../src/termdb.sql.js'
|
|
3
|
+
import Summarystats from '../shared/descriptive.stats.js'
|
|
5
4
|
|
|
6
5
|
export const api: any = {
|
|
7
6
|
endpoint: 'termdb/descrstats',
|
|
@@ -62,7 +61,7 @@ export const api: any = {
|
|
|
62
61
|
|
|
63
62
|
function init({ genomes }) {
|
|
64
63
|
return async (req: any, res: any): Promise<void> => {
|
|
65
|
-
const q = req.query
|
|
64
|
+
const q = req.query as getdescrstatsRequest
|
|
66
65
|
try {
|
|
67
66
|
const g = genomes[req.query.genome]
|
|
68
67
|
if (!g) throw 'invalid genome name'
|
|
@@ -104,42 +103,5 @@ async function trigger_getdescrstats(q: any, res: any, ds: any) {
|
|
|
104
103
|
}
|
|
105
104
|
values.push(value)
|
|
106
105
|
}
|
|
107
|
-
|
|
108
|
-
// compute statistics
|
|
109
|
-
// total
|
|
110
|
-
const total = values.length
|
|
111
|
-
|
|
112
|
-
// mean
|
|
113
|
-
const sum = values.reduce((a, b) => a + b, 0)
|
|
114
|
-
const mean = sum / total
|
|
115
|
-
|
|
116
|
-
// percentiles
|
|
117
|
-
const p25 = computePercentile(values, 25)
|
|
118
|
-
const median = computePercentile(values, 50)
|
|
119
|
-
const p75 = computePercentile(values, 75)
|
|
120
|
-
|
|
121
|
-
// standard deviation
|
|
122
|
-
// get sum of squared differences from mean
|
|
123
|
-
const sumSqDiff = values.map(v => (v - mean) ** 2).reduce((a, b) => a + b, 0)
|
|
124
|
-
// get variance
|
|
125
|
-
const variance = sumSqDiff / (values.length - 1)
|
|
126
|
-
// get standard deviation
|
|
127
|
-
const sd = Math.sqrt(variance)
|
|
128
|
-
|
|
129
|
-
// min/max
|
|
130
|
-
const min = Math.min(...values)
|
|
131
|
-
const max = Math.max(...values)
|
|
132
|
-
|
|
133
|
-
res.send({
|
|
134
|
-
values: [
|
|
135
|
-
{ id: 'total', label: 'n', value: total },
|
|
136
|
-
{ id: 'min', label: 'Minimum', value: roundValue(min, 2) },
|
|
137
|
-
{ id: 'p25', label: '1st quartile', value: roundValue(p25, 2) },
|
|
138
|
-
{ id: 'median', label: 'Median', value: roundValue(median, 2) },
|
|
139
|
-
{ id: 'mean', label: 'Mean', value: roundValue(mean, 2) },
|
|
140
|
-
{ id: 'p75', label: '3rd quartile', value: roundValue(p75, 2) },
|
|
141
|
-
{ id: 'max', label: 'Maximum', value: roundValue(max, 2) },
|
|
142
|
-
{ id: 'sd', label: 'Standard deviation', value: roundValue(sd, 2) }
|
|
143
|
-
]
|
|
144
|
-
})
|
|
106
|
+
res.send(Summarystats(values) as getdescrstatsResponse)
|
|
145
107
|
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getnumericcategoriesRequest,
|
|
3
|
+
getnumericcategoriesResponse
|
|
4
|
+
} from '#shared/types/routes/termdb.getnumericcategories.ts'
|
|
5
|
+
import * as termdbsql from '#src/termdb.sql.js'
|
|
6
|
+
|
|
7
|
+
export const api: any = {
|
|
8
|
+
endpoint: 'termdb/numericcategories',
|
|
9
|
+
methods: {
|
|
10
|
+
get: {
|
|
11
|
+
init,
|
|
12
|
+
request: {
|
|
13
|
+
typeId: 'getnumericcategoriesRequest'
|
|
14
|
+
},
|
|
15
|
+
response: {
|
|
16
|
+
typeId: 'getnumericcategoriesResponse'
|
|
17
|
+
},
|
|
18
|
+
examples: [
|
|
19
|
+
{
|
|
20
|
+
request: {
|
|
21
|
+
body: {
|
|
22
|
+
genome: 'hg38-test',
|
|
23
|
+
dslabel: 'TermdbTest',
|
|
24
|
+
embedder: 'localhost',
|
|
25
|
+
tid: 'aaclassic_5',
|
|
26
|
+
filter: {
|
|
27
|
+
type: 'tvslst',
|
|
28
|
+
in: true,
|
|
29
|
+
join: '',
|
|
30
|
+
lst: [
|
|
31
|
+
{
|
|
32
|
+
tag: 'cohortFilter',
|
|
33
|
+
type: 'tvs',
|
|
34
|
+
tvs: {
|
|
35
|
+
term: {
|
|
36
|
+
name: 'Cohort',
|
|
37
|
+
type: 'categorical',
|
|
38
|
+
values: { ABC: { label: 'ABC' }, XYZ: { label: 'XYZ' } },
|
|
39
|
+
id: 'subcohort',
|
|
40
|
+
isleaf: false,
|
|
41
|
+
groupsetting: { disabled: true }
|
|
42
|
+
},
|
|
43
|
+
values: [{ key: 'ABC', label: 'ABC' }]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
response: {
|
|
51
|
+
header: { status: 200 }
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
post: {
|
|
57
|
+
alternativeFor: 'get',
|
|
58
|
+
init
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function init({ genomes }) {
|
|
64
|
+
return async (req: any, res: any): Promise<void> => {
|
|
65
|
+
const q = req.query as getnumericcategoriesRequest
|
|
66
|
+
try {
|
|
67
|
+
const g = genomes[req.query.genome]
|
|
68
|
+
if (!g) throw 'invalid genome name'
|
|
69
|
+
const ds = g.datasets[req.query.dslabel]
|
|
70
|
+
if (!ds) throw 'invalid dataset name'
|
|
71
|
+
const tdb = ds.cohort.termdb
|
|
72
|
+
if (!tdb) throw 'invalid termdb object'
|
|
73
|
+
|
|
74
|
+
await trigger_getnumericcategories(q, res, tdb, ds) // as getnumericcategoriesResponse
|
|
75
|
+
} catch (e) {
|
|
76
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
77
|
+
// @ts-ignore
|
|
78
|
+
res.send({ error: e?.message || e })
|
|
79
|
+
if (e instanceof Error && e.stack) console.log(e)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function trigger_getnumericcategories(
|
|
85
|
+
q: { tid: any; filter?: any },
|
|
86
|
+
res: { send: (arg0: { lst: any }) => void },
|
|
87
|
+
tdb: { q: { termjsonByOneid: (arg0: any) => any } },
|
|
88
|
+
ds: any
|
|
89
|
+
) {
|
|
90
|
+
if (!q.tid) throw '.tid missing'
|
|
91
|
+
const term = tdb.q.termjsonByOneid(q.tid)
|
|
92
|
+
const arg = {
|
|
93
|
+
ds,
|
|
94
|
+
term_id: q.tid,
|
|
95
|
+
filter: q.filter
|
|
96
|
+
}
|
|
97
|
+
const lst = await termdbsql.get_summary_numericcategories(arg)
|
|
98
|
+
res.send({ lst } as getnumericcategoriesResponse)
|
|
99
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
import * as termdbsql from '
|
|
3
|
-
import computePercentile from '
|
|
1
|
+
import { getpercentileRequest, getpercentileResponse } from '#shared/types/routes/termdb.getpercentile.ts'
|
|
2
|
+
import * as termdbsql from '../src/termdb.sql.js'
|
|
3
|
+
import computePercentile from '../shared/compute.percentile.js'
|
|
4
|
+
import { Filter } from '../shared/types/filter'
|
|
4
5
|
|
|
5
6
|
export const api: any = {
|
|
6
7
|
endpoint: 'termdb/getpercentile',
|
|
@@ -61,7 +62,7 @@ export const api: any = {
|
|
|
61
62
|
|
|
62
63
|
function init({ genomes }) {
|
|
63
64
|
return async (req: any, res: any): Promise<void> => {
|
|
64
|
-
const q = req.query
|
|
65
|
+
const q = req.query as getpercentileRequest
|
|
65
66
|
try {
|
|
66
67
|
const g = genomes[req.query.genome]
|
|
67
68
|
if (!g) throw 'invalid genome name'
|
|
@@ -78,7 +79,7 @@ function init({ genomes }) {
|
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
async function trigger_getpercentile(
|
|
81
|
-
q: { tid: string; getpercentile: number[]; filter:
|
|
82
|
+
q: { tid: string; getpercentile: number[]; filter: Filter },
|
|
82
83
|
res: { send: (arg0: { values: number[] }) => void },
|
|
83
84
|
ds: { cohort: { termdb: { q: { termjsonByOneid: (arg0: any) => any } } } }
|
|
84
85
|
) {
|
|
@@ -113,5 +114,5 @@ async function trigger_getpercentile(
|
|
|
113
114
|
const perc_value = computePercentile(values, percentile)
|
|
114
115
|
perc_values.push(perc_value)
|
|
115
116
|
}
|
|
116
|
-
res.send({ values: perc_values })
|
|
117
|
+
res.send({ values: perc_values } as getpercentileResponse)
|
|
117
118
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { getroottermRequest, getroottermResponse } from '#shared/types/routes/termdb.getrootterm.ts'
|
|
2
|
+
|
|
3
|
+
export const api: any = {
|
|
4
|
+
endpoint: 'termdb/rootterm',
|
|
5
|
+
methods: {
|
|
6
|
+
get: {
|
|
7
|
+
init,
|
|
8
|
+
request: {
|
|
9
|
+
typeId: 'getroottermRequest'
|
|
10
|
+
},
|
|
11
|
+
response: {
|
|
12
|
+
typeId: 'getroottermResponse'
|
|
13
|
+
},
|
|
14
|
+
examples: [
|
|
15
|
+
{
|
|
16
|
+
request: {
|
|
17
|
+
body: {
|
|
18
|
+
genome: 'hg38-test',
|
|
19
|
+
dslabel: 'TermdbTest',
|
|
20
|
+
embedder: 'localhost',
|
|
21
|
+
default_rootterm: 1,
|
|
22
|
+
cohortValues: 'ABC'
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
response: {
|
|
26
|
+
header: { status: 200 }
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
post: {
|
|
32
|
+
alternativeFor: 'get',
|
|
33
|
+
init
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function init({ genomes }) {
|
|
39
|
+
return async (req: any, res: any): Promise<void> => {
|
|
40
|
+
const q = req.query as getroottermRequest
|
|
41
|
+
try {
|
|
42
|
+
const g = genomes[req.query.genome]
|
|
43
|
+
if (!g) throw 'invalid genome name'
|
|
44
|
+
const ds = g.datasets[req.query.dslabel]
|
|
45
|
+
if (!ds) throw 'invalid dataset name'
|
|
46
|
+
const tdb = ds.cohort.termdb
|
|
47
|
+
if (!tdb) throw 'invalid termdb object'
|
|
48
|
+
|
|
49
|
+
await trigger_rootterm(q, res, tdb) // as getroottermResponse
|
|
50
|
+
} catch (e) {
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
52
|
+
// @ts-ignore
|
|
53
|
+
res.send({ error: e?.message || e })
|
|
54
|
+
if (e instanceof Error && e.stack) console.log(e)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function trigger_rootterm(
|
|
60
|
+
q: { cohortValues: any; treeFilter: any },
|
|
61
|
+
res: { send: (arg0: { lst: any }) => void },
|
|
62
|
+
tdb: { q: { getRootTerms: (arg0: any, arg1: any) => any } }
|
|
63
|
+
) {
|
|
64
|
+
const cohortValues = q.cohortValues ? q.cohortValues : ''
|
|
65
|
+
const treeFilter = q.treeFilter ? q.treeFilter : ''
|
|
66
|
+
res.send({ lst: await tdb.q.getRootTerms(cohortValues, treeFilter) } as getroottermResponse)
|
|
67
|
+
}
|