@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjcrh/proteinpaint-server",
3
- "version": "2.138.3-7",
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.135.2-0",
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.138.3-7",
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 { lesionData, processingSummary } = await processSampleData(samples, ds, request);
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 (lesionData.length === 0) {
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(lesionData)
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 ${lesionData.length.toLocaleString()} lesions for analysis`);
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.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
  }
@@ -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
- lesionData: lesions,
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 lesions;
189
+ return {
190
+ sampleLesions: lesions
191
+ };
183
192
  }
184
193
  function filterAndConvertSnvIndel(sampleName, entry, options) {
185
- const opts = {
186
- minTotalDepth: options?.minTotalDepth ?? 10,
187
- minAltAlleleCount: options?.minAltAlleleCount ?? 2,
188
- consequences: options?.consequences ?? [],
189
- hyperMutator: options?.hyperMutator ?? 1e3
190
- };
191
- if (opts.consequences.length > 0 && entry.consequence && !opts.consequences.includes(entry.consequence)) {
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
- const opts = {
204
- lossThreshold: options?.lossThreshold ?? -0.4,
205
- gainThreshold: options?.gainThreshold ?? 0.3,
206
- maxSegLength: options?.maxSegLength ?? 0,
207
- minSegLength: options?.minSegLength ?? 0,
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 = 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
- ];
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
- if (entry.confidence && entry.confidence < opts.minConfidence)
216
+ }
217
+ if (!Number.isInteger(entry.stop)) {
231
218
  return null;
232
- return [
233
- sampleName,
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 normalizeChromosome(chrom) {
241
- return chrom.startsWith("chr") ? chrom : `chr${chrom}`;
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 * 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);