@sjcrh/proteinpaint-server 2.205.1 → 2.206.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 +4 -4
- package/routes/termdb.geneRanking.js +43 -4
- package/routes/termdb.proteome.js +6 -16
- package/src/app.js +179 -99
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjcrh/proteinpaint-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.206.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "a genomics visualization tool for exploring a cohort's genotype and phenotype data",
|
|
6
6
|
"main": "src/app.js",
|
|
@@ -59,9 +59,9 @@
|
|
|
59
59
|
"@sjcrh/augen": "2.204.0",
|
|
60
60
|
"@sjcrh/proteinpaint-python": "2.204.0",
|
|
61
61
|
"@sjcrh/proteinpaint-r": "2.204.0",
|
|
62
|
-
"@sjcrh/proteinpaint-rust": "2.
|
|
63
|
-
"@sjcrh/proteinpaint-shared": "2.
|
|
64
|
-
"@sjcrh/proteinpaint-types": "2.
|
|
62
|
+
"@sjcrh/proteinpaint-rust": "2.206.0",
|
|
63
|
+
"@sjcrh/proteinpaint-shared": "2.206.0",
|
|
64
|
+
"@sjcrh/proteinpaint-types": "2.206.0",
|
|
65
65
|
"@types/express": "^5.0.0",
|
|
66
66
|
"@types/express-session": "^1.18.1",
|
|
67
67
|
"better-sqlite3": "^12.4.1",
|
|
@@ -26,7 +26,7 @@ function stripQuotes(s) {
|
|
|
26
26
|
async function parseTsv(absPath) {
|
|
27
27
|
const text = await fs.promises.readFile(absPath, "utf8");
|
|
28
28
|
const lines = text.split(/\r?\n/).filter((l) => l.length > 0);
|
|
29
|
-
if (lines.length === 0) return { columns: [], rows: [] };
|
|
29
|
+
if (lines.length === 0) return { columns: [], rows: [], counts: [], geneIndex: /* @__PURE__ */ new Map() };
|
|
30
30
|
const columns = lines[0].split(" ").map(stripQuotes);
|
|
31
31
|
const rows = [];
|
|
32
32
|
for (let i = 1; i < lines.length; i++) {
|
|
@@ -39,7 +39,17 @@ async function parseTsv(absPath) {
|
|
|
39
39
|
});
|
|
40
40
|
rows.push(row);
|
|
41
41
|
}
|
|
42
|
-
|
|
42
|
+
const counts = columns.map(
|
|
43
|
+
(_, c) => c === 0 ? rows.length : rows.filter((r) => r[c] !== null && r[c] !== void 0).length
|
|
44
|
+
);
|
|
45
|
+
const geneIndex = /* @__PURE__ */ new Map();
|
|
46
|
+
for (const [i, r] of rows.entries()) {
|
|
47
|
+
if (typeof r[0] === "string") {
|
|
48
|
+
const g = r[0].toLowerCase();
|
|
49
|
+
if (!geneIndex.has(g)) geneIndex.set(g, i);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return { columns, rows, counts, geneIndex };
|
|
43
53
|
}
|
|
44
54
|
function zscorePerColumnIgnoringNull(matrix, ncol) {
|
|
45
55
|
const out = matrix.map((r) => [...r]);
|
|
@@ -67,24 +77,53 @@ async function handleData(q, res, genomes) {
|
|
|
67
77
|
if (!ds) throw "invalid dslabel";
|
|
68
78
|
const cfg = ds.queries?.geneRanking;
|
|
69
79
|
if (!cfg || !cfg.rankings) throw "geneRanking not configured for this dataset";
|
|
80
|
+
if (q.gene) {
|
|
81
|
+
const wanted = String(q.gene).trim().toLowerCase();
|
|
82
|
+
const geneRanks = {};
|
|
83
|
+
const keys = Object.keys(cfg.rankings);
|
|
84
|
+
const loaded = await Promise.all(
|
|
85
|
+
keys.map(
|
|
86
|
+
(key) => loadRanking(q, cfg.rankings[key], key).catch((e) => {
|
|
87
|
+
console.log(`geneRanking: cannot load ranking "${key}": ${e?.message || e}`);
|
|
88
|
+
return null;
|
|
89
|
+
})
|
|
90
|
+
)
|
|
91
|
+
);
|
|
92
|
+
for (const [i, parsed2] of loaded.entries()) {
|
|
93
|
+
if (!parsed2) continue;
|
|
94
|
+
const ri = parsed2.geneIndex.get(wanted);
|
|
95
|
+
geneRanks[keys[i]] = {
|
|
96
|
+
columns: parsed2.columns,
|
|
97
|
+
row: ri === void 0 ? null : parsed2.rows[ri],
|
|
98
|
+
counts: parsed2.counts,
|
|
99
|
+
total: parsed2.rows.length
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
res.send({ geneRanks });
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
70
105
|
if (!q.key) {
|
|
71
106
|
res.send({ keys: Object.keys(cfg.rankings) });
|
|
72
107
|
return;
|
|
73
108
|
}
|
|
74
109
|
const relPath = cfg.rankings[q.key];
|
|
75
110
|
if (!relPath) throw "invalid key";
|
|
111
|
+
const parsed = await loadRanking(q, relPath, q.key);
|
|
112
|
+
res.send({ columns: parsed.columns, rows: parsed.rows });
|
|
113
|
+
}
|
|
114
|
+
async function loadRanking(q, relPath, key) {
|
|
76
115
|
if (path.isAbsolute(relPath) || relPath.split(/[\\/]/).includes("..")) throw "invalid file path";
|
|
77
116
|
const absPath = path.resolve(serverconfig.tpmasterdir, relPath);
|
|
78
117
|
const tpRoot = path.resolve(serverconfig.tpmasterdir) + path.sep;
|
|
79
118
|
if (!absPath.startsWith(tpRoot)) throw "invalid file path";
|
|
80
119
|
const stat = await fs.promises.stat(absPath);
|
|
81
|
-
const cacheKey = `${q.genome}|${q.dslabel}|${
|
|
120
|
+
const cacheKey = `${q.genome}|${q.dslabel}|${key}`;
|
|
82
121
|
let entry = fileCache.get(cacheKey);
|
|
83
122
|
if (!entry || entry.mtimeMs !== stat.mtimeMs) {
|
|
84
123
|
entry = { parsed: await parseTsv(absPath), mtimeMs: stat.mtimeMs };
|
|
85
124
|
fileCache.set(cacheKey, entry);
|
|
86
125
|
}
|
|
87
|
-
|
|
126
|
+
return entry.parsed;
|
|
88
127
|
}
|
|
89
128
|
async function handleCluster(q, res) {
|
|
90
129
|
const { row_names, col_names } = q;
|
|
@@ -3,19 +3,7 @@ import { get_ds_tdb } from "#src/termdb.js";
|
|
|
3
3
|
import * as utils from "#src/utils.js";
|
|
4
4
|
import { mayLimitSamples } from "#src/mds3.filter.js";
|
|
5
5
|
import serverconfig from "#src/serverconfig.js";
|
|
6
|
-
import { readGeneRows } from "../src/routes/termdb.bubbleHeatmap.ts";
|
|
7
|
-
function baseUniProtAcc(acc) {
|
|
8
|
-
if (!acc) return "";
|
|
9
|
-
if (acc.includes("|")) {
|
|
10
|
-
const parts2 = acc.split("|");
|
|
11
|
-
const id = parts2.length >= 2 ? parts2[1] : acc;
|
|
12
|
-
const dash = id.indexOf("-");
|
|
13
|
-
return dash > 0 ? id.slice(0, dash) : id;
|
|
14
|
-
}
|
|
15
|
-
const parts = acc.split(".");
|
|
16
|
-
if (parts.length >= 3 && (parts[0] === "sp" || parts[0] === "tr")) return parts[1];
|
|
17
|
-
return acc;
|
|
18
|
-
}
|
|
6
|
+
import { readGeneRows, baseUniProtAcc } from "../src/routes/termdb.bubbleHeatmap.ts";
|
|
19
7
|
const missingDapWarned = /* @__PURE__ */ new Set();
|
|
20
8
|
function init({ genomes }) {
|
|
21
9
|
return async (req, res) => {
|
|
@@ -93,6 +81,8 @@ function init({ genomes }) {
|
|
|
93
81
|
for (const row of rows) {
|
|
94
82
|
const entry = {
|
|
95
83
|
organism: organismName,
|
|
84
|
+
// the study catalog (dataset config) is the authority on disease
|
|
85
|
+
disease: cohortCfg.catalog?.disease,
|
|
96
86
|
assayName,
|
|
97
87
|
cohortName,
|
|
98
88
|
uniqueIdentifier: row.identifier,
|
|
@@ -101,8 +91,8 @@ function init({ genomes }) {
|
|
|
101
91
|
// client computes log2(foldChange); DAP stores log2FC directly
|
|
102
92
|
foldChange: Math.pow(2, row.fc),
|
|
103
93
|
// significance is the DAP file's FDR (BH-adjusted p), consistent with
|
|
104
|
-
// the other DAP-driven tools
|
|
105
|
-
//
|
|
94
|
+
// the other DAP-driven tools; a nominal p is only present when the
|
|
95
|
+
// DAP file carries one (6th column)
|
|
106
96
|
fdr: row.fdr,
|
|
107
97
|
pValue: row.fdr,
|
|
108
98
|
testedN: caseSamples.length,
|
|
@@ -130,7 +120,7 @@ function init({ genomes }) {
|
|
|
130
120
|
const baseAcc = baseUniProtAcc(e.proteinAccession);
|
|
131
121
|
if (!baseAcc) continue;
|
|
132
122
|
const key = `${e.organism}|${e.cohortName}|${baseAcc}`;
|
|
133
|
-
const p = Number.isFinite(e.
|
|
123
|
+
const p = Number.isFinite(e.fdr) ? e.fdr : Infinity;
|
|
134
124
|
const cur = refFcByKey.get(key);
|
|
135
125
|
if (!cur || p < cur.p) refFcByKey.set(key, { fc: e.foldChange, p });
|
|
136
126
|
}
|