@sjcrh/proteinpaint-server 2.138.3-5 → 2.139.0

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.
@@ -114,10 +114,11 @@ function termdb_test_default() {
114
114
  name: "Demographics",
115
115
  plots: [
116
116
  {
117
- chartType: "barchart",
118
- settings: { barchart: { colorBars: true, showPercent: true } },
117
+ chartType: "violin",
118
+ settings: { violin: { showStats: false } },
119
119
  term: {
120
- id: "agedx"
120
+ id: "agedx",
121
+ q: { mode: "continuous" }
121
122
  }
122
123
  },
123
124
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjcrh/proteinpaint-server",
3
- "version": "2.138.3-5",
3
+ "version": "2.139.0",
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.135.2-0",
63
+ "@sjcrh/proteinpaint-python": "2.139.0",
64
64
  "@sjcrh/proteinpaint-r": "2.137.2-0",
65
- "@sjcrh/proteinpaint-rust": "2.137.2-0",
66
- "@sjcrh/proteinpaint-shared": "2.137.3",
67
- "@sjcrh/proteinpaint-types": "2.138.3-5",
65
+ "@sjcrh/proteinpaint-rust": "2.138.3-7",
66
+ "@sjcrh/proteinpaint-shared": "2.138.3-7",
67
+ "@sjcrh/proteinpaint-types": "2.139.0",
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");
@@ -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.stdout);
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
  }
@@ -125,21 +126,60 @@ async function processSampleData(samples, ds, request) {
125
126
  failedSamples: 0,
126
127
  failedFiles: []
127
128
  };
129
+ const dataTypeCounts = {
130
+ [dtsnvindel]: { samples: 0, entries: 0, processedLesions: 0 },
131
+ [dtcnv]: { samples: 0, entries: 0, processedLesions: 0 },
132
+ [dtfusionrna]: { samples: 0, entries: 0, processedLesions: 0 },
133
+ unknown: /* @__PURE__ */ new Map()
134
+ };
128
135
  mayLog(`[GRIN2] Processing JSON files for ${samples.length.toLocaleString()} samples`);
129
136
  for (const sample of samples) {
130
137
  try {
131
138
  const filepath = path.join(serverconfig.tpmasterdir, ds.queries.singleSampleMutation.folder, sample.name);
132
139
  await file_is_readable(filepath);
133
140
  const mlst = JSON.parse(await read_file(filepath));
134
- const sampleLesions = await processSampleMlst(sample.name, mlst, lesionId, request);
141
+ const { sampleLesions, sampleDataTypeCounts } = await processSampleMlst(sample.name, mlst, lesionId, request);
135
142
  lesions.push(...sampleLesions);
136
143
  lesionId += sampleLesions.length;
144
+ for (const [dt, counts] of Object.entries(sampleDataTypeCounts.known)) {
145
+ if (counts.entries > 0) {
146
+ dataTypeCounts[dt].samples++;
147
+ dataTypeCounts[dt].entries += counts.entries;
148
+ dataTypeCounts[dt].processedLesions += counts.processedLesions;
149
+ }
150
+ }
151
+ for (const [dt, counts] of sampleDataTypeCounts.unknown) {
152
+ if (!dataTypeCounts.unknown.has(dt)) {
153
+ dataTypeCounts.unknown.set(dt, { samples: /* @__PURE__ */ new Set(), entries: 0 });
154
+ }
155
+ dataTypeCounts.unknown.get(dt).samples.add(sample.name);
156
+ dataTypeCounts.unknown.get(dt).entries += counts;
157
+ }
158
+ processingSummary.successfulSamples++;
137
159
  } catch (error) {
160
+ processingSummary.failedSamples++;
161
+ processingSummary.failedFiles?.push(sample.name);
138
162
  mayLog(
139
163
  `[GRIN2] Error processing sample ${sample.name}: ${typeof error === "object" && error !== null && "message" in error ? error.message : String(error)}`
140
164
  );
141
165
  }
142
166
  }
167
+ mayLog(`[GRIN2] === DATA TYPE ANALYSIS SUMMARY ===`);
168
+ mayLog(
169
+ `[GRIN2] SNV/Indel (dt=${dtsnvindel}): ${dataTypeCounts[dtsnvindel].samples} samples, ${dataTypeCounts[dtsnvindel].entries} entries, ${dataTypeCounts[dtsnvindel].processedLesions} lesions`
170
+ );
171
+ mayLog(
172
+ `[GRIN2] CNV (dt=${dtcnv}): ${dataTypeCounts[dtcnv].samples} samples, ${dataTypeCounts[dtcnv].entries} entries, ${dataTypeCounts[dtcnv].processedLesions} lesions`
173
+ );
174
+ mayLog(
175
+ `[GRIN2] Fusion (dt=${dtfusionrna}): ${dataTypeCounts[dtfusionrna].samples} samples, ${dataTypeCounts[dtfusionrna].entries} entries, ${dataTypeCounts[dtfusionrna].processedLesions} lesions`
176
+ );
177
+ if (dataTypeCounts.unknown.size > 0) {
178
+ mayLog(`[GRIN2] Unknown data types:`);
179
+ for (const [dt, counts] of dataTypeCounts.unknown) {
180
+ mayLog(`[GRIN2] dt="${dt}": ${counts.samples.size} samples, ${counts.entries} entries`);
181
+ }
182
+ }
143
183
  mayLog(`[GRIN2] Total lesions processed: ${lesions.length.toLocaleString()}`);
144
184
  return {
145
185
  lesionData: lesions,
@@ -148,38 +188,60 @@ async function processSampleData(samples, ds, request) {
148
188
  }
149
189
  async function processSampleMlst(sampleName, mlst, startId, request) {
150
190
  const lesions = [];
191
+ const sampleDataTypeCounts = {
192
+ known: {
193
+ [dtsnvindel]: { entries: 0, processedLesions: 0 },
194
+ [dtcnv]: { entries: 0, processedLesions: 0 },
195
+ [dtfusionrna]: { entries: 0, processedLesions: 0 }
196
+ },
197
+ unknown: /* @__PURE__ */ new Map()
198
+ };
151
199
  for (const m of mlst) {
152
200
  switch (m.dt) {
153
201
  case dtsnvindel: {
202
+ sampleDataTypeCounts.known[dtsnvindel].entries++;
154
203
  if (!request.snvindelOptions)
155
204
  break;
156
205
  const snvIndelLesion = filterAndConvertSnvIndel(sampleName, m, request.snvindelOptions);
157
- if (snvIndelLesion)
206
+ if (snvIndelLesion) {
158
207
  lesions.push(snvIndelLesion);
208
+ sampleDataTypeCounts.known[dtsnvindel].processedLesions++;
209
+ }
159
210
  break;
160
211
  }
161
212
  case dtcnv: {
213
+ sampleDataTypeCounts.known[dtcnv].entries++;
162
214
  if (!request.cnvOptions)
163
215
  break;
164
216
  const cnvLesion = filterAndConvertCnv(sampleName, m, request.cnvOptions);
165
- if (cnvLesion)
217
+ if (cnvLesion) {
166
218
  lesions.push(cnvLesion);
219
+ sampleDataTypeCounts.known[dtcnv].processedLesions++;
220
+ }
167
221
  break;
168
222
  }
169
223
  case dtfusionrna: {
224
+ sampleDataTypeCounts.known[dtfusionrna].entries++;
170
225
  if (!request.fusionOptions)
171
226
  break;
172
227
  const fusionLesion = filterAndConvertFusion(sampleName, m, request.fusionOptions);
173
- if (fusionLesion)
228
+ if (fusionLesion) {
174
229
  lesions.push(fusionLesion);
230
+ sampleDataTypeCounts.known[dtfusionrna].processedLesions++;
231
+ }
175
232
  break;
176
233
  }
177
- default:
178
- mayLog(`[GRIN2] Unknown data type "${m.dt}" in sample ${sampleName}, skipping entry`);
234
+ default: {
235
+ const currentCount = sampleDataTypeCounts.unknown.get(m.dt) || 0;
236
+ sampleDataTypeCounts.unknown.set(m.dt, currentCount + 1);
179
237
  break;
238
+ }
180
239
  }
181
240
  }
182
- return lesions;
241
+ return {
242
+ sampleLesions: lesions,
243
+ sampleDataTypeCounts
244
+ };
183
245
  }
184
246
  function filterAndConvertSnvIndel(sampleName, entry, options) {
185
247
  const opts = {
@@ -188,16 +250,27 @@ function filterAndConvertSnvIndel(sampleName, entry, options) {
188
250
  consequences: options?.consequences ?? [],
189
251
  hyperMutator: options?.hyperMutator ?? 1e3
190
252
  };
191
- if (opts.consequences.length > 0 && entry.consequence && !opts.consequences.includes(entry.consequence)) {
253
+ if (opts.consequences.length > 0 && entry.class && !opts.consequences.includes(entry.class)) {
192
254
  return null;
193
255
  }
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
- ];
256
+ const chromosome = entry.chromosome || entry.chr;
257
+ const position = entry.start || entry.position || entry.pos;
258
+ if (!chromosome) {
259
+ return null;
260
+ }
261
+ if (position === void 0 || position === null || position === "undefined") {
262
+ return null;
263
+ }
264
+ const numPosition = parseInt(String(position));
265
+ if (isNaN(numPosition)) {
266
+ return null;
267
+ }
268
+ const endPosition = entry.end || position;
269
+ const numEndPosition = parseInt(String(endPosition));
270
+ if (isNaN(numEndPosition)) {
271
+ return null;
272
+ }
273
+ return [sampleName, normalizeChromosome(chromosome), String(numPosition), String(numEndPosition), "mutation"];
201
274
  }
202
275
  function filterAndConvertCnv(sampleName, entry, options) {
203
276
  const opts = {
@@ -207,18 +280,38 @@ function filterAndConvertCnv(sampleName, entry, options) {
207
280
  minSegLength: options?.minSegLength ?? 0,
208
281
  hyperMutator: options?.hyperMutator ?? 500
209
282
  };
210
- const isGain = entry.log2Ratio >= opts.gainThreshold;
211
- const isLoss = entry.log2Ratio <= opts.lossThreshold;
212
- if (!isGain && !isLoss)
283
+ const log2Ratio = entry.log2Ratio ?? entry.value ?? entry.ratio;
284
+ if (log2Ratio === void 0 || log2Ratio === null) {
213
285
  return null;
214
- const lesionType = entry.log2Ratio >= opts.gainThreshold ? "gain" : "loss";
215
- return [
216
- sampleName,
217
- normalizeChromosome(entry.chromosome || entry.chr),
218
- String(entry.start || entry.begin),
219
- String(entry.end || entry.stop),
220
- lesionType
221
- ];
286
+ }
287
+ const numLog2Ratio = parseFloat(String(log2Ratio));
288
+ if (isNaN(numLog2Ratio)) {
289
+ return null;
290
+ }
291
+ const isGain = numLog2Ratio >= opts.gainThreshold;
292
+ const isLoss = numLog2Ratio <= opts.lossThreshold;
293
+ if (!isGain && !isLoss) {
294
+ return null;
295
+ }
296
+ const chromosome = entry.chromosome || entry.chr;
297
+ const start = entry.start || entry.begin;
298
+ const end = entry.end || entry.stop;
299
+ if (!chromosome) {
300
+ return null;
301
+ }
302
+ if (start === void 0 || start === null || start === "undefined") {
303
+ return null;
304
+ }
305
+ if (end === void 0 || end === null || end === "undefined") {
306
+ return null;
307
+ }
308
+ const numStart = parseInt(String(start));
309
+ const numEnd = parseInt(String(end));
310
+ if (isNaN(numStart) || isNaN(numEnd)) {
311
+ return null;
312
+ }
313
+ const lesionType = numLog2Ratio >= opts.gainThreshold ? "gain" : "loss";
314
+ return [sampleName, normalizeChromosome(chromosome), String(numStart), String(numEnd), lesionType];
222
315
  }
223
316
  function filterAndConvertFusion(sampleName, entry, options) {
224
317
  const opts = {
@@ -238,6 +331,8 @@ function filterAndConvertFusion(sampleName, entry, options) {
238
331
  ];
239
332
  }
240
333
  function normalizeChromosome(chrom) {
334
+ if (!chrom)
335
+ return "chr?";
241
336
  return chrom.startsWith("chr") ? chrom : `chr${chrom}`;
242
337
  }
243
338
  export {
@@ -265,10 +265,12 @@ function getAllowedTermTypes(ds) {
265
265
  for (const t of ds.cohort.termdb.allowedTermTypes)
266
266
  typeSet.add(t);
267
267
  }
268
- if (ds?.queries?.geneExpression)
268
+ if (ds.queries?.geneExpression)
269
269
  typeSet.add(TermTypes.GENE_EXPRESSION);
270
- if (ds?.queries?.metaboliteIntensity)
270
+ if (ds.queries?.metaboliteIntensity)
271
271
  typeSet.add(TermTypes.METABOLITE_INTENSITY);
272
+ if (ds.queries?.ssGSEA)
273
+ typeSet.add(TermTypes.SSGSEA);
272
274
  return [...typeSet];
273
275
  }
274
276
  function getSelectCohort(ds, req) {
@@ -21,34 +21,30 @@ function init({ genomes }) {
21
21
  if (!g)
22
22
  throw "invalid genome name";
23
23
  const ds = g.datasets?.[req.query.dslabel];
24
- getFilters(req.query, ds, res);
24
+ res.send(await getFilters(req.query, ds));
25
25
  } catch (e) {
26
- console.log(e);
26
+ if (e.stack)
27
+ console.log(e.stack);
27
28
  res.send({ status: "error", error: e.message || e });
28
29
  }
29
30
  };
30
31
  }
31
- async function getFilters(query, ds, res) {
32
+ async function getFilters(query, ds) {
32
33
  if (!query.filterByUserSites)
33
34
  authApi.mayAdjustFilter(query, ds, query.terms);
34
- try {
35
- const samplesPerFilter = await getSamplesPerFilter(query, ds);
36
- const filtersData = await getData(
37
- {
38
- terms: query.terms,
39
- __protected__: query.__protected__
40
- },
41
- ds
42
- );
43
- const tw2List = {};
44
- for (const tw of query.terms) {
45
- tw2List[tw.term.id] = getList(samplesPerFilter, filtersData, tw, query.showAll);
46
- }
47
- res.send({ ...tw2List });
48
- } catch (e) {
49
- console.log(e);
50
- res.send({ error: e.message || e });
35
+ const samplesPerFilter = await getSamplesPerFilter(query, ds);
36
+ const filtersData = await getData(
37
+ {
38
+ terms: query.terms,
39
+ __protected__: query.__protected__
40
+ },
41
+ ds
42
+ );
43
+ const tw2List = {};
44
+ for (const tw of query.terms) {
45
+ tw2List[tw.term.id] = getList(samplesPerFilter, filtersData, tw, query.showAll);
51
46
  }
47
+ return { ...tw2List };
52
48
  }
53
49
  function getList(samplesPerFilter, filtersData, tw, showAll) {
54
50
  const values = Object.values(tw.term.values);
@@ -1,5 +1,6 @@
1
1
  import { percentilePayload } from "#types/checkers";
2
- import * as termdbsql from "#src/termdb.sql.js";
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 = ds.cohort.termdb.q.termjsonByOneid(q.tid);
37
+ const term = q.term;
37
38
  if (!term)
38
- throw "invalid termid";
39
- if (term.type != "float" && term.type != "integer")
40
- throw "not numerical term";
41
- const percentile_lst = q.getpercentile;
42
- const perc_values = [];
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 rows = await termdbsql.get_rows_by_one_key({
45
- ds,
46
- key: q.tid,
47
- filter: q.filter ? typeof q.filter == "string" ? JSON.parse(q.filter) : q.filter : null
48
- });
49
- for (const { value } of rows) {
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);