@sjcrh/proteinpaint-server 2.185.0 → 2.187.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.
@@ -26,42 +26,47 @@ function init({ genomes }) {
26
26
  const term = q.term?.term || q.term;
27
27
  if (!term?.name) throw "term.name missing";
28
28
  const cohorts = [];
29
- for (const assayName in ds.queries.proteome.assays) {
30
- const assay = ds.queries.proteome.assays[assayName];
31
- for (const cohortName in assay.cohorts || {}) {
32
- const details = {
33
- dbfile: ds.queries.proteome.dbfile,
34
- assay: assayName,
35
- cohort: cohortName
36
- };
37
- const tw = {
38
- $id: "_",
39
- term: {
40
- name: term.name,
41
- type: "proteomeAbundance",
42
- proteomeDetails: details
29
+ for (const organismName in ds.queries.proteome.organisms) {
30
+ const organism = ds.queries.proteome.organisms[organismName];
31
+ for (const assayName in organism.assays) {
32
+ const assay = organism.assays[assayName];
33
+ for (const cohortName in assay.cohorts || {}) {
34
+ const details = {
35
+ dbfile: ds.queries.proteome.dbfile,
36
+ organism: organismName,
37
+ assay: assayName,
38
+ cohort: cohortName
39
+ };
40
+ const tw = {
41
+ $id: "_",
42
+ term: {
43
+ name: term.name,
44
+ type: "proteomeAbundance",
45
+ proteomeDetails: details
46
+ }
47
+ };
48
+ const cohortData = await ds.queries.proteome.get({
49
+ terms: [tw],
50
+ proteomeDetails: details,
51
+ filter: q.filter,
52
+ filter0: q.filter0,
53
+ for: "proteinView",
54
+ __abortSignal: q.__abortSignal
55
+ });
56
+ const controlSampleIds = cohortData.controlSampleIds || /* @__PURE__ */ new Set();
57
+ const prior = assay.cohorts[cohortName].prior;
58
+ for (const entry of cohortData.allEntries || []) {
59
+ const s2v = entry.s2v;
60
+ const stats = getCohortStats(s2v, controlSampleIds, prior);
61
+ delete entry.s2v;
62
+ entry.foldChange = stats.foldChange;
63
+ entry.pValue = stats.pValue;
64
+ entry.testedN = stats.testedN;
65
+ entry.controlN = stats.controlN;
66
+ if (assay.mclassOverride) entry.mclassOverride = assay.mclassOverride;
67
+ if (organism.genomeName) entry.genomeName = organism.genomeName;
68
+ cohorts.push(entry);
43
69
  }
44
- };
45
- const cohortData = await ds.queries.proteome.get({
46
- terms: [tw],
47
- proteomeDetails: details,
48
- filter: q.filter,
49
- filter0: q.filter0,
50
- for: "proteinView",
51
- __abortSignal: q.__abortSignal
52
- });
53
- const controlSampleIds = cohortData.controlSampleIds || /* @__PURE__ */ new Set();
54
- const prior = assay.cohorts[cohortName].prior;
55
- for (const entry of cohortData.allEntries || []) {
56
- const s2v = entry.s2v;
57
- const stats = getCohortStats(s2v, controlSampleIds, prior);
58
- delete entry.s2v;
59
- entry.foldChange = stats.foldChange;
60
- entry.pValue = stats.pValue;
61
- entry.testedN = stats.testedN;
62
- entry.controlN = stats.controlN;
63
- if (assay.mclassOverride) entry.mclassOverride = assay.mclassOverride;
64
- cohorts.push(entry);
65
70
  }
66
71
  }
67
72
  }
@@ -202,8 +207,8 @@ function lnGamma(z) {
202
207
  async function validate_query_proteome(ds) {
203
208
  const q = ds.queries.proteome;
204
209
  if (!q) return;
205
- if (!q.assays) {
206
- throw "queries.proteome.assays is missing";
210
+ if (!q.organisms) {
211
+ throw "queries.proteome.organisms is missing";
207
212
  }
208
213
  if (!q.dbfile) {
209
214
  throw "queries.proteome.dbfile is missing";
@@ -213,21 +218,31 @@ async function validate_query_proteome(ds) {
213
218
  } catch (e) {
214
219
  throw `Cannot connect to proteome db ${q.dbfile}: ${e.message || e}`;
215
220
  }
216
- for (const assayName in q.assays) {
217
- const assay = q.assays[assayName];
218
- if (assay.columnIdx == null) throw `queries.proteome.assays.${assayName}.columnIdx missing`;
219
- if (assay.columnValue == null) throw `queries.proteome.assays.${assayName}.columnValue missing`;
220
- if (assay.cohorts) {
221
- for (const cohortName in assay.cohorts) {
222
- const cohort = assay.cohorts[cohortName];
223
- if (!cohort.controlFilter)
224
- throw `Missing controlFilter in queries.proteome.assays.${assayName}.cohorts.${cohortName}`;
225
- if (!cohort.caseFilter) throw `Missing caseFilter in queries.proteome.assays.${assayName}.cohorts.${cohortName}`;
226
- if (!cohort.prior?.d0 || !cohort.prior?.s0sq)
227
- throw `Missing prior.d0 and prior.s0sq in queries.proteome.assays.${assayName}.cohorts.${cohortName}`;
221
+ for (const organismName in q.organisms) {
222
+ const organism = q.organisms[organismName];
223
+ if (organism.columnIdx == null) throw `queries.proteome.organisms.${organismName}.columnIdx missing`;
224
+ if (organism.columnValue == null) throw `queries.proteome.organisms.${organismName}.columnValue missing`;
225
+ if (!organism.assays || typeof organism.assays != "object")
226
+ throw `queries.proteome.organisms.${organismName}.assays missing or invalid`;
227
+ for (const assayName in organism.assays) {
228
+ const assay = organism.assays[assayName];
229
+ if (assay.columnIdx == null)
230
+ throw `queries.proteome.organisms.${organismName}.assays.${assayName}.columnIdx missing`;
231
+ if (assay.columnValue == null)
232
+ throw `queries.proteome.organisms.${organismName}.assays.${assayName}.columnValue missing`;
233
+ if (assay.cohorts) {
234
+ for (const cohortName in assay.cohorts) {
235
+ const cohort = assay.cohorts[cohortName];
236
+ if (!cohort.controlFilter)
237
+ throw `Missing controlFilter in queries.proteome.organisms.${organismName}.assays.${assayName}.cohorts.${cohortName}`;
238
+ if (!cohort.caseFilter)
239
+ throw `Missing caseFilter in queries.proteome.organisms.${organismName}.assays.${assayName}.cohorts.${cohortName}`;
240
+ if (!cohort.prior?.d0 || !cohort.prior?.s0sq)
241
+ throw `Missing prior.d0 and prior.s0sq in queries.proteome.organisms.${organismName}.assays.${assayName}.cohorts.${cohortName}`;
242
+ }
243
+ } else {
244
+ throw `Invalid assay structure for "${assayName}". Must have .cohorts`;
228
245
  }
229
- } else {
230
- throw `Invalid assay structure for "${assayName}". Must have .cohorts`;
231
246
  }
232
247
  }
233
248
  q.find = async (arg) => {
@@ -235,22 +250,27 @@ async function validate_query_proteome(ds) {
235
250
  if (!Array.isArray(proteins) || proteins.length == 0) throw "queries.proteome.find arg.proteins[] missing";
236
251
  const matches = /* @__PURE__ */ new Set();
237
252
  const details = arg?.proteomeDetails || {};
253
+ const organism = details.organism;
238
254
  const assay = details.assay;
239
255
  const cohort = details.cohort;
240
256
  const MAX_FIND_RESULTS = 500;
241
257
  const filters = [];
242
258
  if (Object.keys(details).length) {
243
- if (!assay || !cohort) throw "queries.proteome.find arg.proteomeDetails.{assay,cohort} missing";
244
- const assayConfig = q.assays?.[assay];
259
+ if (!organism || !assay || !cohort)
260
+ throw "queries.proteome.find arg.proteomeDetails.{organism,assay,cohort} missing";
261
+ const organismConfig = q.organisms?.[organism];
262
+ if (!organismConfig) throw `queries.proteome.find invalid organism: ${organism}`;
263
+ const assayConfig = organismConfig.assays?.[assay];
245
264
  if (!assayConfig) throw `queries.proteome.find invalid assay: ${assay}`;
246
265
  const cohortConfig = assayConfig?.cohorts?.[cohort];
247
266
  if (!cohortConfig) throw `queries.proteome.find invalid cohort: ${cohort}`;
267
+ const organismFilter = [{ columnIdx: organismConfig.columnIdx, columnValue: organismConfig.columnValue }];
248
268
  const assayFilter = [{ columnIdx: assayConfig.columnIdx, columnValue: assayConfig.columnValue }];
249
269
  const cohortFilter = (Array.isArray(cohortConfig.caseFilter) ? cohortConfig.caseFilter : []).filter(
250
270
  (filter) => !!filter
251
271
  );
252
272
  if (!cohortFilter.length) throw `queries.proteome.find invalid cohort caseFilter: ${cohort}`;
253
- filters.push(...assayFilter, ...cohortFilter);
273
+ filters.push(...organismFilter, ...assayFilter, ...cohortFilter);
254
274
  }
255
275
  for (const p of proteins) {
256
276
  if (!p) continue;
@@ -280,8 +300,8 @@ async function validate_query_proteome(ds) {
280
300
  };
281
301
  q.get = async (param) => {
282
302
  if (!param?.terms?.length) throw "queries.proteome.get param.terms[] missing";
283
- if (!param.proteomeDetails?.assay || !param.proteomeDetails?.cohort)
284
- throw "queries.proteome.get param.proteomeDetails.{assay,cohort} missing";
303
+ if (!param.proteomeDetails?.assay || !param.proteomeDetails?.cohort || !param.proteomeDetails?.organism)
304
+ throw "queries.proteome.get param.proteomeDetails.{assay,cohort,organism} missing";
285
305
  return await getProteomeValuesFromCohort(ds, param, q);
286
306
  };
287
307
  }
@@ -312,23 +332,28 @@ function buildFilterClause(filters) {
312
332
  function queryDbRows(db, matchColumn, matchValue, filters) {
313
333
  const { conditions, params } = buildFilterClause(filters);
314
334
  const allConditions = [`${matchColumn} = ? COLLATE NOCASE`, ...conditions];
315
- const sql = `SELECT identifier, protein_accession, isoform, modsite, gene, sample, value
335
+ const sql = `SELECT organism, disease, identifier, protein_accession, isoform, modsite, gene, sample, value
316
336
  FROM proteome_abundance
317
337
  WHERE ${allConditions.join(" AND ")}`;
318
338
  return db.prepare(sql).all(matchValue, ...params);
319
339
  }
320
340
  async function getProteomeValuesFromCohort(ds, param, q) {
321
341
  const db = ds.queries.proteome.db;
322
- const { assay, cohort } = param.proteomeDetails;
323
- const assayConfig = q.assays?.[assay];
342
+ const { assay, cohort, organism } = param.proteomeDetails;
343
+ const organismConfig = q.organisms?.[organism];
344
+ if (!organismConfig) throw `queries.proteome invalid organism: ${organism}`;
345
+ const organismColumnIdx = organismConfig.columnIdx;
346
+ const organismColumnValue = organismConfig.columnValue;
347
+ const assayConfig = organismConfig.assays?.[assay];
324
348
  if (!assayConfig) throw `queries.proteome.get invalid assay: ${assay}`;
325
- const PTMType = q.assays[assay].PTMType;
349
+ const PTMType = assayConfig.PTMType;
326
350
  const assayColumnIdx = assayConfig.columnIdx;
327
351
  const assayColumnValue = assayConfig.columnValue;
328
352
  const cohortConfig = assayConfig?.cohorts?.[cohort];
329
353
  if (!cohortConfig) throw `queries.proteome.get invalid cohort: ${cohort}`;
330
354
  const cohortControlFilter = cohortConfig.controlFilter;
331
355
  const cohortCaseFilter = cohortConfig.caseFilter;
356
+ const organismFilter = [{ columnIdx: organismColumnIdx, columnValue: organismColumnValue }];
332
357
  const assayFilter = [{ columnIdx: assayColumnIdx, columnValue: assayColumnValue }];
333
358
  const term2sample2value = /* @__PURE__ */ new Map();
334
359
  const allEntries = [];
@@ -346,8 +371,12 @@ async function getProteomeValuesFromCohort(ds, param, q) {
346
371
  }
347
372
  const matchColumn = param.for === "proteinView" ? "gene" : "identifier";
348
373
  const matchValue = param.for === "proteinView" ? geneName : identifier;
349
- const caseRows = queryDbRows(db, matchColumn, matchValue, [...assayFilter, ...cohortCaseFilter]);
350
- const controlRows = queryDbRows(db, matchColumn, matchValue, [...assayFilter, ...cohortControlFilter]);
374
+ const caseRows = queryDbRows(db, matchColumn, matchValue, [...organismFilter, ...assayFilter, ...cohortCaseFilter]);
375
+ const controlRows = queryDbRows(db, matchColumn, matchValue, [
376
+ ...organismFilter,
377
+ ...assayFilter,
378
+ ...cohortControlFilter
379
+ ]);
351
380
  for (const row of controlRows) {
352
381
  const sid = ds.cohort.termdb.q.sampleName2id(row.sample);
353
382
  if (sid !== void 0) controlSampleIds.add(String(sid));
@@ -359,8 +388,8 @@ async function getProteomeValuesFromCohort(ds, param, q) {
359
388
  if (sid !== void 0) allSampleIds.push(sid);
360
389
  }
361
390
  const uniqueSampleIds = [...new Set(allSampleIds)];
362
- const limitSamples = await mayLimitSamples(param, uniqueSampleIds, ds);
363
- if (limitSamples?.size == 0) {
391
+ const allowedSampleIds = await mayLimitSamples(param, uniqueSampleIds, ds);
392
+ if (allowedSampleIds?.size == 0) {
364
393
  return { term2sample2value: /* @__PURE__ */ new Map(), byTermId: {}, bySampleId: {} };
365
394
  }
366
395
  if (param.for === "proteinView") {
@@ -368,9 +397,11 @@ async function getProteomeValuesFromCohort(ds, param, q) {
368
397
  for (const row of allRows) {
369
398
  const sid = ds.cohort.termdb.q.sampleName2id(row.sample);
370
399
  if (sid === void 0) continue;
371
- if (limitSamples && !limitSamples.has(sid)) continue;
400
+ if (allowedSampleIds && !allowedSampleIds.has(sid)) continue;
372
401
  if (!entryMap.has(row.identifier)) {
373
402
  entryMap.set(row.identifier, {
403
+ organism: row.organism,
404
+ disease: row.disease,
374
405
  uniqueIdentifier: row.identifier,
375
406
  assayName: assay,
376
407
  cohortName: cohort,
@@ -391,7 +422,7 @@ async function getProteomeValuesFromCohort(ds, param, q) {
391
422
  for (const row of allRows) {
392
423
  const sid = ds.cohort.termdb.q.sampleName2id(row.sample);
393
424
  if (sid === void 0) continue;
394
- if (limitSamples && !limitSamples.has(sid)) continue;
425
+ if (allowedSampleIds && !allowedSampleIds.has(sid)) continue;
395
426
  s2v[sid] = row.value;
396
427
  }
397
428
  if (Object.keys(s2v).length) {
@@ -135,23 +135,24 @@ function validateDataNative(D, ds) {
135
135
  }
136
136
  const file2Lines = {};
137
137
  D.get = async (q) => {
138
+ const sampleId = q.sample?.eID || q.sample?.sID;
138
139
  if (q.checkPlotAvailability) {
139
140
  const plots2 = [];
140
141
  for (const plot of D.plots) {
141
142
  if (!q.plots.includes(plot.name)) continue;
142
- const tsvfile = path.join(
143
- serverconfig.tpmasterdir,
144
- plot.folder,
145
- (q.sample?.eID || q.sample?.sID) + (plot.fileSuffix || "")
146
- );
143
+ const tsvfile = path.join(serverconfig.tpmasterdir, plot.folder, sampleId + (plot.fileSuffix || ""));
147
144
  try {
148
145
  await file_is_readable(tsvfile);
149
- plots2.push({
150
- name: plot.name,
151
- expCells: [],
152
- // FIXME avoid breaking client but shouldn't be needed
153
- noExpCells: []
154
- });
146
+ plots2.push({ name: plot.name });
147
+ } catch (_) {
148
+ }
149
+ }
150
+ const imgs = ds.queries.singleCell?.images;
151
+ if (imgs) {
152
+ const imgFile = path.join(serverconfig.tpmasterdir, imgs.folder, sampleId, imgs.fileName);
153
+ try {
154
+ await file_is_readable(imgFile);
155
+ plots2.push({ name: imgs?.label || "Image" });
155
156
  } catch (_) {
156
157
  }
157
158
  }
@@ -166,11 +167,7 @@ function validateDataNative(D, ds) {
166
167
  }
167
168
  for (const plot of D.plots) {
168
169
  if (!q.plots.includes(plot.name)) continue;
169
- const tsvfile = path.join(
170
- serverconfig.tpmasterdir,
171
- plot.folder,
172
- (q.sample?.eID || q.sample?.sID) + (plot.fileSuffix || "")
173
- );
170
+ const tsvfile = path.join(serverconfig.tpmasterdir, plot.folder, sampleId + (plot.fileSuffix || ""));
174
171
  if (!file2Lines[tsvfile]) {
175
172
  await file_is_readable(tsvfile);
176
173
  const text = await read_file(tsvfile);
@@ -71,7 +71,6 @@ function expandNumericTermCollection(q, data) {
71
71
  throw new Error("overlayTw is not supported with numeric termCollection; member terms are used as the overlay");
72
72
  if (q.divideTw) throw new Error("divideTw is not supported with numeric termCollection");
73
73
  const termlst = term.termlst || [];
74
- mayLog("termlst", termlst);
75
74
  mayLog(
76
75
  `Expanding numeric termCollection with ${termlst.length} member terms and ${Object.keys(data.samples).length} samples`
77
76
  );