@sjcrh/proteinpaint-server 2.138.3-7 → 2.139.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 +3 -3
- package/routes/grin2.js +43 -61
- package/routes/termdb.percentile.js +19 -13
- package/src/app.js +104 -110
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjcrh/proteinpaint-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.139.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",
|
|
@@ -60,11 +60,11 @@
|
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@sjcrh/augen": "2.136.0",
|
|
63
|
-
"@sjcrh/proteinpaint-python": "2.
|
|
63
|
+
"@sjcrh/proteinpaint-python": "2.139.1",
|
|
64
64
|
"@sjcrh/proteinpaint-r": "2.137.2-0",
|
|
65
65
|
"@sjcrh/proteinpaint-rust": "2.138.3-7",
|
|
66
66
|
"@sjcrh/proteinpaint-shared": "2.138.3-7",
|
|
67
|
-
"@sjcrh/proteinpaint-types": "2.
|
|
67
|
+
"@sjcrh/proteinpaint-types": "2.139.1",
|
|
68
68
|
"@types/express": "^5.0.0",
|
|
69
69
|
"@types/express-session": "^1.18.1",
|
|
70
70
|
"better-sqlite3": "^9.4.1",
|
package/routes/grin2.js
CHANGED
|
@@ -23,6 +23,7 @@ function init({ genomes }) {
|
|
|
23
23
|
return async (req, res) => {
|
|
24
24
|
try {
|
|
25
25
|
const request = req.query;
|
|
26
|
+
console.log("[GRIN2] request:", request);
|
|
26
27
|
const g = genomes[request.genome];
|
|
27
28
|
if (!g)
|
|
28
29
|
throw new Error("genome missing");
|
|
@@ -59,7 +60,7 @@ async function runGrin2(g, ds, request) {
|
|
|
59
60
|
}
|
|
60
61
|
mayLog("[GRIN2] Processing sample data...");
|
|
61
62
|
const processingStartTime = Date.now();
|
|
62
|
-
const {
|
|
63
|
+
const { lesions, processingSummary } = await processSampleData(samples, ds, request);
|
|
63
64
|
const processingTime = Date.now() - processingStartTime;
|
|
64
65
|
const processingTimeToPrint = Math.round(processingTime / 1e3);
|
|
65
66
|
mayLog(`[GRIN2] Data processing took ${processingTimeToPrint} seconds`);
|
|
@@ -69,13 +70,13 @@ async function runGrin2(g, ds, request) {
|
|
|
69
70
|
if (processingSummary && processingSummary.failedSamples > 0) {
|
|
70
71
|
mayLog(`[GRIN2] Warning: ${processingSummary.failedSamples} samples failed to process`);
|
|
71
72
|
}
|
|
72
|
-
if (
|
|
73
|
+
if (lesions.length === 0) {
|
|
73
74
|
throw new Error("No lesions found after processing all samples. Check filter criteria and input data.");
|
|
74
75
|
}
|
|
75
76
|
const pyInput = {
|
|
76
77
|
genedb: path.join(serverconfig.tpmasterdir, g.genedb.dbfile),
|
|
77
78
|
chromosomelist: {},
|
|
78
|
-
lesion: JSON.stringify(
|
|
79
|
+
lesion: JSON.stringify(lesions)
|
|
79
80
|
};
|
|
80
81
|
for (const c in g.majorchr) {
|
|
81
82
|
if (ds.queries.singleSampleMutation.discoPlot?.skipChrM) {
|
|
@@ -84,7 +85,7 @@ async function runGrin2(g, ds, request) {
|
|
|
84
85
|
}
|
|
85
86
|
pyInput.chromosomelist[c] = g.majorchr[c];
|
|
86
87
|
}
|
|
87
|
-
mayLog(`[GRIN2] Prepared ${
|
|
88
|
+
mayLog(`[GRIN2] Prepared ${lesions.length.toLocaleString()} lesions for analysis`);
|
|
88
89
|
const grin2AnalysisStart = Date.now();
|
|
89
90
|
const pyResult = await run_python("grin2PpWrapper.py", JSON.stringify(pyInput));
|
|
90
91
|
if (pyResult.stderr?.trim()) {
|
|
@@ -96,7 +97,7 @@ async function runGrin2(g, ds, request) {
|
|
|
96
97
|
const grin2AnalysisTime = Date.now() - grin2AnalysisStart;
|
|
97
98
|
const grin2AnalysisTimeToPrint = Math.round(grin2AnalysisTime / 1e3);
|
|
98
99
|
mayLog(`[GRIN2] Python processing took ${grin2AnalysisTimeToPrint} seconds`);
|
|
99
|
-
const resultData = JSON.parse(pyResult
|
|
100
|
+
const resultData = JSON.parse(pyResult);
|
|
100
101
|
if (!resultData?.png?.[0]) {
|
|
101
102
|
throw new Error("Invalid Python output: missing PNG data");
|
|
102
103
|
}
|
|
@@ -131,10 +132,13 @@ async function processSampleData(samples, ds, request) {
|
|
|
131
132
|
const filepath = path.join(serverconfig.tpmasterdir, ds.queries.singleSampleMutation.folder, sample.name);
|
|
132
133
|
await file_is_readable(filepath);
|
|
133
134
|
const mlst = JSON.parse(await read_file(filepath));
|
|
134
|
-
const sampleLesions = await processSampleMlst(sample.name, mlst, lesionId, request);
|
|
135
|
+
const { sampleLesions } = await processSampleMlst(sample.name, mlst, lesionId, request);
|
|
135
136
|
lesions.push(...sampleLesions);
|
|
136
137
|
lesionId += sampleLesions.length;
|
|
138
|
+
processingSummary.successfulSamples++;
|
|
137
139
|
} catch (error) {
|
|
140
|
+
processingSummary.failedSamples++;
|
|
141
|
+
processingSummary.failedFiles?.push(sample.name);
|
|
138
142
|
mayLog(
|
|
139
143
|
`[GRIN2] Error processing sample ${sample.name}: ${typeof error === "object" && error !== null && "message" in error ? error.message : String(error)}`
|
|
140
144
|
);
|
|
@@ -142,7 +146,7 @@ async function processSampleData(samples, ds, request) {
|
|
|
142
146
|
}
|
|
143
147
|
mayLog(`[GRIN2] Total lesions processed: ${lesions.length.toLocaleString()}`);
|
|
144
148
|
return {
|
|
145
|
-
|
|
149
|
+
lesions,
|
|
146
150
|
processingSummary
|
|
147
151
|
};
|
|
148
152
|
}
|
|
@@ -154,91 +158,69 @@ async function processSampleMlst(sampleName, mlst, startId, request) {
|
|
|
154
158
|
if (!request.snvindelOptions)
|
|
155
159
|
break;
|
|
156
160
|
const snvIndelLesion = filterAndConvertSnvIndel(sampleName, m, request.snvindelOptions);
|
|
157
|
-
if (snvIndelLesion)
|
|
161
|
+
if (snvIndelLesion) {
|
|
158
162
|
lesions.push(snvIndelLesion);
|
|
163
|
+
}
|
|
159
164
|
break;
|
|
160
165
|
}
|
|
161
166
|
case dtcnv: {
|
|
162
167
|
if (!request.cnvOptions)
|
|
163
168
|
break;
|
|
164
169
|
const cnvLesion = filterAndConvertCnv(sampleName, m, request.cnvOptions);
|
|
165
|
-
if (cnvLesion)
|
|
170
|
+
if (cnvLesion) {
|
|
166
171
|
lesions.push(cnvLesion);
|
|
172
|
+
}
|
|
167
173
|
break;
|
|
168
174
|
}
|
|
169
175
|
case dtfusionrna: {
|
|
170
176
|
if (!request.fusionOptions)
|
|
171
177
|
break;
|
|
172
178
|
const fusionLesion = filterAndConvertFusion(sampleName, m, request.fusionOptions);
|
|
173
|
-
if (fusionLesion)
|
|
179
|
+
if (fusionLesion) {
|
|
174
180
|
lesions.push(fusionLesion);
|
|
181
|
+
}
|
|
175
182
|
break;
|
|
176
183
|
}
|
|
177
|
-
default:
|
|
178
|
-
mayLog(`[GRIN2] Unknown data type "${m.dt}" in sample ${sampleName}, skipping entry`);
|
|
184
|
+
default: {
|
|
179
185
|
break;
|
|
186
|
+
}
|
|
180
187
|
}
|
|
181
188
|
}
|
|
182
|
-
return
|
|
189
|
+
return {
|
|
190
|
+
sampleLesions: lesions
|
|
191
|
+
};
|
|
183
192
|
}
|
|
184
193
|
function filterAndConvertSnvIndel(sampleName, entry, options) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
if (
|
|
194
|
+
if (!options?.consequences) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
if (options.consequences.length > 0 && entry.class && !options.consequences.includes(entry.class)) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
if (!Number.isInteger(entry.pos)) {
|
|
192
201
|
return null;
|
|
193
202
|
}
|
|
194
|
-
return [
|
|
195
|
-
sampleName,
|
|
196
|
-
normalizeChromosome(entry.chromosome || entry.chr),
|
|
197
|
-
String(entry.start || entry.position),
|
|
198
|
-
String(entry.end || entry.position),
|
|
199
|
-
"mutation"
|
|
200
|
-
];
|
|
203
|
+
return [sampleName, entry.chr, entry.pos, entry.pos, "mutation"];
|
|
201
204
|
}
|
|
202
205
|
function filterAndConvertCnv(sampleName, entry, options) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
hyperMutator: options?.hyperMutator ?? 500
|
|
209
|
-
};
|
|
210
|
-
const isGain = entry.log2Ratio >= opts.gainThreshold;
|
|
211
|
-
const isLoss = entry.log2Ratio <= opts.lossThreshold;
|
|
206
|
+
if (!options || options.gainThreshold === void 0 || options.lossThreshold === void 0) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
const isGain = entry.value >= options.gainThreshold;
|
|
210
|
+
const isLoss = entry.value <= options.lossThreshold;
|
|
212
211
|
if (!isGain && !isLoss)
|
|
213
212
|
return null;
|
|
214
|
-
const lesionType =
|
|
215
|
-
|
|
216
|
-
sampleName,
|
|
217
|
-
normalizeChromosome(entry.chromosome || entry.chr),
|
|
218
|
-
String(entry.start || entry.begin),
|
|
219
|
-
String(entry.end || entry.stop),
|
|
220
|
-
lesionType
|
|
221
|
-
];
|
|
222
|
-
}
|
|
223
|
-
function filterAndConvertFusion(sampleName, entry, options) {
|
|
224
|
-
const opts = {
|
|
225
|
-
fusionTypes: options?.fusionTypes ?? ["gene-gene", "gene-intergenic", "readthrough"],
|
|
226
|
-
minConfidence: options?.minConfidence ?? 0.7
|
|
227
|
-
};
|
|
228
|
-
if (entry.fusionType && !opts.fusionTypes.includes(entry.fusionType))
|
|
213
|
+
const lesionType = isGain ? "gain" : "loss";
|
|
214
|
+
if (!Number.isInteger(entry.start)) {
|
|
229
215
|
return null;
|
|
230
|
-
|
|
216
|
+
}
|
|
217
|
+
if (!Number.isInteger(entry.stop)) {
|
|
231
218
|
return null;
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
normalizeChromosome(entry.chromosome || entry.chr),
|
|
235
|
-
String(entry.start || entry.position),
|
|
236
|
-
String(entry.end || entry.position),
|
|
237
|
-
"fusion"
|
|
238
|
-
];
|
|
219
|
+
}
|
|
220
|
+
return [sampleName, entry.chr, entry.start, entry.stop, lesionType];
|
|
239
221
|
}
|
|
240
|
-
function
|
|
241
|
-
return
|
|
222
|
+
function filterAndConvertFusion(sampleName, entry, _options) {
|
|
223
|
+
return [sampleName, entry.chrA, entry.posA, entry.posB, "fusion"];
|
|
242
224
|
}
|
|
243
225
|
export {
|
|
244
226
|
api
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { percentilePayload } from "#types/checkers";
|
|
2
|
-
import
|
|
2
|
+
import { isNumericTerm } from "#shared/terms.js";
|
|
3
|
+
import { getData } from "#src/termdb.matrix.js";
|
|
3
4
|
import computePercentile from "#shared/compute.percentile.js";
|
|
4
5
|
const api = {
|
|
5
6
|
endpoint: "termdb/getpercentile",
|
|
@@ -33,20 +34,23 @@ function init({ genomes }) {
|
|
|
33
34
|
};
|
|
34
35
|
}
|
|
35
36
|
async function trigger_getpercentile(q, res, ds) {
|
|
36
|
-
const term =
|
|
37
|
+
const term = q.term;
|
|
37
38
|
if (!term)
|
|
38
|
-
throw "
|
|
39
|
-
if (term
|
|
40
|
-
throw "not
|
|
41
|
-
const
|
|
42
|
-
const
|
|
39
|
+
throw "term is missing";
|
|
40
|
+
if (!isNumericTerm(term))
|
|
41
|
+
throw "not numeric term";
|
|
42
|
+
const tw = { $id: "_", term, q: { mode: "continuous" } };
|
|
43
|
+
const data = await getData({ filter: q.filter, filter0: q.filter0, terms: [tw], __protected__: q.__protected__ }, ds);
|
|
44
|
+
if (data.error)
|
|
45
|
+
throw data.error;
|
|
43
46
|
const values = [];
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
for (const key in data.samples) {
|
|
48
|
+
const sample = data.samples[key];
|
|
49
|
+
const v = sample[tw.$id];
|
|
50
|
+
if (!v?.value) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const value = v.value;
|
|
50
54
|
if (term.values && term.values[value] && term.values[value].uncomputable) {
|
|
51
55
|
continue;
|
|
52
56
|
}
|
|
@@ -55,6 +59,8 @@ async function trigger_getpercentile(q, res, ds) {
|
|
|
55
59
|
}
|
|
56
60
|
values.push(Number(value));
|
|
57
61
|
}
|
|
62
|
+
const percentile_lst = q.getpercentile;
|
|
63
|
+
const perc_values = [];
|
|
58
64
|
for (const percentile of percentile_lst) {
|
|
59
65
|
const perc_value = computePercentile(values, percentile);
|
|
60
66
|
perc_values.push(perc_value);
|