@sjcrh/proteinpaint-server 2.204.0 → 2.205.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.
@@ -437,10 +437,6 @@ function termdb_test_default() {
437
437
  folder: "files/hg38/TermdbTest/scrna/geneExpHdf5"
438
438
  }
439
439
  },
440
- WSImages: {
441
- type: "H&E",
442
- imageBySampleFolder: "files/hg38/TermdbTest/wsimages"
443
- },
444
440
  // w2 plot. folder: spatial images, one per subfolder of the sample's
445
441
  // directory (folder/<sample>/<imageName>/), files inside found by the
446
442
  // *FileSuffix fields. wsiFolder: plain whole-slide images, laid out as
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjcrh/proteinpaint-server",
3
- "version": "2.204.0",
3
+ "version": "2.205.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,30 +59,26 @@
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.204.0",
63
- "@sjcrh/proteinpaint-shared": "2.204.0",
64
- "@sjcrh/proteinpaint-types": "2.204.0",
62
+ "@sjcrh/proteinpaint-rust": "2.205.1",
63
+ "@sjcrh/proteinpaint-shared": "2.205.0",
64
+ "@sjcrh/proteinpaint-types": "2.205.0",
65
65
  "@types/express": "^5.0.0",
66
66
  "@types/express-session": "^1.18.1",
67
67
  "better-sqlite3": "^12.4.1",
68
68
  "body-parser": "^1.15.2",
69
69
  "canvas": "~3.2.0",
70
70
  "compression": "^1.6.2",
71
- "connect-redis": "^6.1.3",
72
71
  "cookie-parser": "^1.4.5",
73
72
  "d3": "^7.6.1",
74
73
  "deep-object-diff": "^1.1.0",
75
74
  "express": "^4.17.1",
76
75
  "express-basic-auth": "^1.1.5",
77
76
  "express-session": "^1.18.1",
78
- "got": "^14.2.0",
79
77
  "jsonwebtoken": "^9.0.3",
80
78
  "ky": "^1.2.1",
81
79
  "micromatch": "^4.0.5",
82
80
  "minimatch": "^10.0.1",
83
- "node-fetch": "^2.6.1",
84
81
  "partjson": "^0.58.2",
85
- "redis": "^4.7.0",
86
82
  "skia-canvas": "~3.0.8",
87
83
  "tiny-async-pool": "^1.2.0",
88
84
  "tough-cookie": "^4.1.4"
@@ -20,9 +20,9 @@ function init({ genomes }) {
20
20
  res.send({
21
21
  data: {
22
22
  [group1Name]: groups.group1names.length,
23
- [group2Name]: groups.group2names.length,
24
- ...groups.alerts.length ? { alert: groups.alerts.join(" | ") } : {}
25
- }
23
+ [group2Name]: groups.group2names.length
24
+ },
25
+ ...groups.alerts.length ? { alert: groups.alerts.join(" | ") } : {}
26
26
  });
27
27
  return;
28
28
  }
@@ -1,13 +1,22 @@
1
+ import path from "path";
1
2
  import { get_ds_tdb } from "#src/termdb.js";
2
3
  import * as utils from "#src/utils.js";
3
4
  import { mayLimitSamples } from "#src/mds3.filter.js";
5
+ import serverconfig from "#src/serverconfig.js";
6
+ import { readGeneRows } from "../src/routes/termdb.bubbleHeatmap.ts";
4
7
  function baseUniProtAcc(acc) {
5
8
  if (!acc) return "";
6
- const parts = acc.split("|");
7
- const id = parts.length >= 2 ? parts[1] : acc;
8
- const dash = id.indexOf("-");
9
- return dash > 0 ? id.slice(0, dash) : id;
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;
10
18
  }
19
+ const missingDapWarned = /* @__PURE__ */ new Set();
11
20
  function init({ genomes }) {
12
21
  return async (req, res) => {
13
22
  const q = req.query;
@@ -15,63 +24,99 @@ function init({ genomes }) {
15
24
  const genome = genomes[q.genome];
16
25
  if (!genome) throw "invalid genome";
17
26
  const [ds] = get_ds_tdb(genome, q);
18
- if (!ds.queries?.proteome?.get) throw "queries.proteome.get() missing";
27
+ if (!ds.queries?.proteome?.organisms) throw "queries.proteome not configured";
19
28
  const term = q.term?.term || q.term;
20
29
  if (!term?.name) throw "term.name missing";
21
30
  const cohorts = [];
22
31
  const brConfig = ds.queries.proteome.brainRegions;
23
32
  const regionRemap = brConfig?.regionValueRemap || {};
24
33
  const sampleRegions = {};
34
+ const identifierAnno = /* @__PURE__ */ new Map();
25
35
  for (const organismName in ds.queries.proteome.organisms) {
26
36
  const organism = ds.queries.proteome.organisms[organismName];
27
37
  for (const assayName in organism.assays) {
28
38
  const assay = organism.assays[assayName];
29
39
  for (const cohortName in assay.cohorts || {}) {
30
- const details = {
31
- dbfile: ds.queries.proteome.dbfile,
32
- organism: organismName,
33
- assay: assayName,
34
- cohort: cohortName
35
- };
36
- const tw = {
37
- $id: "_",
38
- term: {
39
- name: term.name,
40
- type: "proteomeAbundance",
41
- dataTypeDetails: details
40
+ const cohortCfg = assay.cohorts[cohortName];
41
+ const dapPath = path.join(serverconfig.tpmasterdir, cohortCfg.DAPfile);
42
+ const rows = await readGeneRows(dapPath, String(term.name).toLowerCase());
43
+ if (!rows) {
44
+ if (!missingDapWarned.has(dapPath)) {
45
+ missingDapWarned.add(dapPath);
46
+ console.warn(
47
+ `proteome: DAPfile missing or unreadable for ${organismName}/${assayName}/${cohortName}: ${dapPath}`
48
+ );
49
+ }
50
+ continue;
51
+ }
52
+ if (!rows.length) continue;
53
+ const organismFilter = [{ columnIdx: organism.columnIdx, columnValue: organism.columnValue }];
54
+ const assayFilter = [{ columnIdx: assay.columnIdx, columnValue: assay.columnValue }];
55
+ let caseSamples = [];
56
+ let controlSamples = [];
57
+ try {
58
+ caseSamples = listCohortSamples(ds.queries.proteome.db, [
59
+ ...organismFilter,
60
+ ...assayFilter,
61
+ ...cohortCfg.caseFilter
62
+ ]);
63
+ controlSamples = listCohortSamples(ds.queries.proteome.db, [
64
+ ...organismFilter,
65
+ ...assayFilter,
66
+ ...cohortCfg.controlFilter
67
+ ]);
68
+ } catch {
69
+ }
70
+ const annoKey = `${organismName}|${assayName}`;
71
+ if (!identifierAnno.has(annoKey)) {
72
+ let m = /* @__PURE__ */ new Map();
73
+ try {
74
+ m = listIdentifierAnnotations(ds.queries.proteome.db, term.name, [...organismFilter, ...assayFilter]);
75
+ } catch {
76
+ }
77
+ identifierAnno.set(annoKey, m);
78
+ }
79
+ const anno = identifierAnno.get(annoKey);
80
+ const sampleIds = brConfig ? [...caseSamples, ...controlSamples] : [];
81
+ if (brConfig) {
82
+ const regionOf = (filters) => {
83
+ const f = filters.find((f2) => f2.columnIdx === brConfig.regionColumnIdx);
84
+ if (!f) return void 0;
85
+ const code = regionRemap[String(f.columnValue)] ?? String(f.columnValue);
86
+ return brConfig.regions[code] !== void 0 ? code : void 0;
87
+ };
88
+ const caseRegion = regionOf(cohortCfg.caseFilter);
89
+ const controlRegion = regionOf(cohortCfg.controlFilter);
90
+ if (caseRegion) for (const sid of caseSamples) sampleRegions[sid] = caseRegion;
91
+ if (controlRegion) for (const sid of controlSamples) sampleRegions[sid] = controlRegion;
92
+ }
93
+ for (const row of rows) {
94
+ const entry = {
95
+ organism: organismName,
96
+ assayName,
97
+ cohortName,
98
+ uniqueIdentifier: row.identifier,
99
+ proteinAccession: row.acc,
100
+ geneName: term.name,
101
+ // client computes log2(foldChange); DAP stores log2FC directly
102
+ foldChange: Math.pow(2, row.fc),
103
+ // significance is the DAP file's FDR (BH-adjusted p), consistent with
104
+ // the other DAP-driven tools. pValue is a deprecated alias of fdr kept
105
+ // for existing clients; new code should read fdr.
106
+ fdr: row.fdr,
107
+ pValue: row.fdr,
108
+ testedN: caseSamples.length,
109
+ controlN: controlSamples.length
110
+ };
111
+ const a = anno.get(row.identifier);
112
+ if (a?.isoform) entry.isoform = a.isoform;
113
+ if (assay.PTMType) {
114
+ entry.PTMType = assay.PTMType;
115
+ if (a?.modsite) entry.modSites = a.modsite;
42
116
  }
43
- };
44
- const cohortData = await ds.queries.proteome.get({
45
- terms: [tw],
46
- dataTypeDetails: details,
47
- filter: q.filter,
48
- filter0: q.filter0,
49
- for: "proteinView",
50
- __abortSignal: q.__abortSignal
51
- });
52
- const controlSampleIds = cohortData.controlSampleIds || /* @__PURE__ */ new Set();
53
- const prior = assay.cohorts[cohortName].prior;
54
- for (const entry of cohortData.allEntries || []) {
55
- const s2v = entry.s2v;
56
- const sampleRegionsRaw = entry.sampleRegionsRaw || {};
57
- const stats = getCohortStats(s2v, controlSampleIds, prior);
58
- delete entry.s2v;
59
- delete entry.sampleRegionsRaw;
60
- entry.foldChange = stats.foldChange;
61
- entry.pValue = stats.pValue;
62
- entry.testedN = stats.testedN;
63
- entry.controlN = stats.controlN;
64
117
  if (assay.mclassOverride) entry.mclassOverride = assay.mclassOverride;
65
118
  if (organism.genomeName) entry.genomeName = organism.genomeName;
66
- if (brConfig) {
67
- entry.sampleIds = Object.keys(s2v);
68
- for (const sid in sampleRegionsRaw) {
69
- const raw = sampleRegionsRaw[sid];
70
- if (raw == null) continue;
71
- const code = regionRemap[String(raw)] ?? String(raw);
72
- if (brConfig.regions[code] !== void 0) sampleRegions[sid] = code;
73
- }
74
- }
119
+ if (brConfig) entry.sampleIds = sampleIds.slice();
75
120
  cohorts.push(entry);
76
121
  }
77
122
  }
@@ -104,133 +149,6 @@ function init({ genomes }) {
104
149
  }
105
150
  };
106
151
  }
107
- function getCohortStats(allS2v, controlSampleIds, prior) {
108
- if (!allS2v || typeof allS2v != "object") return { foldChange: null, pValue: null, testedN: 0, controlN: 0 };
109
- const controlValues = [];
110
- const testedValues = [];
111
- for (const sampleId in allS2v) {
112
- const v = Number(allS2v[sampleId]);
113
- if (!Number.isFinite(v)) continue;
114
- if (controlSampleIds.has(String(sampleId))) controlValues.push(v);
115
- else testedValues.push(v);
116
- }
117
- const controlMean = controlValues?.length ? controlValues.reduce((sum, v) => sum + v, 0) / controlValues.length : null;
118
- const testedMean = testedValues?.length ? testedValues.reduce((sum, v) => sum + v, 0) / testedValues.length : null;
119
- const foldChange = testedMean != null && controlMean != null && Number.isFinite(testedMean) && Number.isFinite(controlMean) && controlMean !== 0 ? testedMean / controlMean : null;
120
- if (!Number.isFinite(prior?.d0) || prior.d0 <= 0 || !Number.isFinite(prior?.s0sq) || prior.s0sq <= 0) {
121
- throw "prior with finite positive d0 and s0sq is required for moderated t-test";
122
- }
123
- const pValue = getModeratedPValue(testedValues, controlValues, prior);
124
- return {
125
- foldChange,
126
- pValue,
127
- testedN: testedValues.length,
128
- controlN: controlValues.length
129
- };
130
- }
131
- function getModeratedPValue(a, b, prior) {
132
- const n1 = a.length;
133
- const n2 = b.length;
134
- if (n1 < 2 || n2 < 2) return null;
135
- const mean1 = a.reduce((s, v) => s + v, 0) / n1;
136
- const mean2 = b.reduce((s, v) => s + v, 0) / n2;
137
- let ss1 = 0;
138
- for (const v of a) {
139
- const d = v - mean1;
140
- ss1 += d * d;
141
- }
142
- let ss2 = 0;
143
- for (const v of b) {
144
- const d = v - mean2;
145
- ss2 += d * d;
146
- }
147
- const dfResidual = n1 + n2 - 2;
148
- const pooledVar = (ss1 + ss2) / dfResidual;
149
- const { d0, s0sq } = prior;
150
- const sTildeSq = (d0 * s0sq + dfResidual * pooledVar) / (d0 + dfResidual);
151
- const se = Math.sqrt(sTildeSq * (1 / n1 + 1 / n2));
152
- if (!(se > 0)) {
153
- if (mean1 === mean2) return 1;
154
- return 1e-300;
155
- }
156
- const t = (mean1 - mean2) / se;
157
- const df = d0 + dfResidual;
158
- if (!Number.isFinite(df) || df < 0.1) return null;
159
- const p = 2 * tCdfTail(Math.abs(t), df);
160
- if (!Number.isFinite(p)) return null;
161
- return Math.max(1e-300, Math.min(1, p));
162
- }
163
- function tCdfTail(t, df) {
164
- const x = df / (df + t * t);
165
- return 0.5 * regularizedBetaIncomplete(df / 2, 0.5, x);
166
- }
167
- function regularizedBetaIncomplete(a, b, x) {
168
- if (x <= 0) return 0;
169
- if (x >= 1) return 1;
170
- if (x > (a + 1) / (a + b + 2)) {
171
- return 1 - regularizedBetaIncomplete(b, a, 1 - x);
172
- }
173
- const lnPrefactor = lnBetaPrefactor(a, b, x);
174
- const maxIter = 200;
175
- const eps = 1e-14;
176
- let f = 1e-30;
177
- let C = 1e-30;
178
- let D = 0;
179
- for (let m = 0; m <= maxIter; m++) {
180
- let numerator;
181
- if (m === 0) {
182
- numerator = 1;
183
- } else {
184
- const k = m;
185
- if (k % 2 === 1) {
186
- const i = (k - 1) / 2;
187
- numerator = -(a + i) * (a + b + i) * x / ((a + 2 * i) * (a + 2 * i + 1));
188
- } else {
189
- const i = k / 2;
190
- numerator = i * (b - i) * x / ((a + 2 * i - 1) * (a + 2 * i));
191
- }
192
- }
193
- D = 1 + numerator * D;
194
- if (Math.abs(D) < 1e-30) D = 1e-30;
195
- D = 1 / D;
196
- C = 1 + numerator / C;
197
- if (Math.abs(C) < 1e-30) C = 1e-30;
198
- const delta = C * D;
199
- f *= delta;
200
- if (m > 0 && Math.abs(delta - 1) < eps) break;
201
- }
202
- return Math.exp(lnPrefactor) * f / a;
203
- }
204
- function lnBetaPrefactor(a, b, x) {
205
- return a * Math.log(x) + b * Math.log(1 - x) - lnBeta(a, b);
206
- }
207
- function lnBeta(a, b) {
208
- return lnGamma(a) + lnGamma(b) - lnGamma(a + b);
209
- }
210
- function lnGamma(z) {
211
- if (z < 0.5) {
212
- return Math.log(Math.PI / Math.sin(Math.PI * z)) - lnGamma(1 - z);
213
- }
214
- z -= 1;
215
- const g = 7;
216
- const coef = [
217
- 0.9999999999998099,
218
- 676.5203681218851,
219
- -1259.1392167224028,
220
- 771.3234287776531,
221
- -176.6150291621406,
222
- 12.507343278686905,
223
- -0.13857109526572012,
224
- 9984369578019572e-21,
225
- 15056327351493116e-23
226
- ];
227
- let x = coef[0];
228
- for (let i = 1; i < coef.length; i++) {
229
- x += coef[i] / (z + i);
230
- }
231
- const t = z + g + 0.5;
232
- return 0.5 * Math.log(2 * Math.PI) + (z + 0.5) * Math.log(t) - t + Math.log(x);
233
- }
234
152
  async function validate_query_proteome(ds) {
235
153
  const q = ds.queries.proteome;
236
154
  if (!q) return;
@@ -264,14 +182,15 @@ async function validate_query_proteome(ds) {
264
182
  throw `Missing controlFilter in queries.proteome.organisms.${organismName}.assays.${assayName}.cohorts.${cohortName}`;
265
183
  if (!cohort.caseFilter)
266
184
  throw `Missing caseFilter in queries.proteome.organisms.${organismName}.assays.${assayName}.cohorts.${cohortName}`;
267
- if (!cohort.DAPfile && (!cohort.prior?.d0 || !cohort.prior?.s0sq))
268
- throw `Missing prior.d0 and prior.s0sq in queries.proteome.organisms.${organismName}.assays.${assayName}.cohorts.${cohortName}`;
185
+ if (!cohort.DAPfile)
186
+ throw `Missing DAPfile in queries.proteome.organisms.${organismName}.assays.${assayName}.cohorts.${cohortName}`;
269
187
  }
270
188
  } else {
271
189
  throw `Invalid assay structure for "${assayName}". Must have .cohorts`;
272
190
  }
273
191
  }
274
192
  }
193
+ const geneIndexHint = q.db.prepare(`SELECT 1 FROM sqlite_master WHERE type = 'index' AND name = 'proteome_abundance_gene'`).get() ? " INDEXED BY proteome_abundance_gene" : "";
275
194
  q.find = async (arg) => {
276
195
  const proteins = arg?.proteins;
277
196
  if (!Array.isArray(proteins) || proteins.length == 0) throw "queries.proteome.find arg.proteins[] missing";
@@ -307,7 +226,7 @@ async function validate_query_proteome(ds) {
307
226
  const rawRows = [];
308
227
  if (filters?.length) {
309
228
  const { conditions, params } = buildFilterClause(filters);
310
- const sql = `SELECT DISTINCT gene, identifier FROM proteome_abundance WHERE gene >= ? COLLATE NOCASE AND gene < ? COLLATE NOCASE AND ${conditions.join(
229
+ const sql = `SELECT DISTINCT gene, identifier FROM proteome_abundance${geneIndexHint} WHERE gene >= ? COLLATE NOCASE AND gene < ? COLLATE NOCASE AND ${conditions.join(
311
230
  " AND "
312
231
  )} LIMIT ${MAX_FIND_RESULTS}`;
313
232
  rawRows.push(...q.db.prepare(sql).all(token, upperToken, ...params));
@@ -356,23 +275,36 @@ function buildFilterClause(filters) {
356
275
  }
357
276
  return { conditions, params };
358
277
  }
359
- function countDistinctSamples(db, filters) {
360
- if (!filters?.length) throw "countDistinctSamples: filters must not be empty";
278
+ function listCohortSamples(db, filters) {
279
+ if (!filters?.length) throw "listCohortSamples: filters must not be empty";
280
+ let perDb = cohortSampleCache.get(db);
281
+ if (!perDb) cohortSampleCache.set(db, perDb = /* @__PURE__ */ new Map());
282
+ const key = JSON.stringify(filters);
283
+ const hit = perDb.get(key);
284
+ if (hit) return hit;
361
285
  const { conditions, params } = buildFilterClause(filters);
362
- const row = db.prepare(`SELECT COUNT(DISTINCT sample) as cnt FROM proteome_abundance WHERE ${conditions.join(" AND ")}`).get(...params);
363
- return row?.cnt || 0;
286
+ const samples = db.prepare(`SELECT DISTINCT sample FROM proteome_abundance WHERE ${conditions.join(" AND ")}`).all(...params).map((r) => String(r.sample));
287
+ perDb.set(key, samples);
288
+ return samples;
364
289
  }
365
- function queryDbRows(db, matchColumn, matchValue, filters) {
290
+ const cohortSampleCache = /* @__PURE__ */ new WeakMap();
291
+ function listIdentifierAnnotations(db, gene, filters) {
366
292
  const { conditions, params } = buildFilterClause(filters);
367
- const allConditions = [`${matchColumn} = ? COLLATE NOCASE`, ...conditions];
293
+ const rows = db.prepare(
294
+ `SELECT identifier, modsite, isoform FROM proteome_abundance WHERE gene = ? COLLATE NOCASE${conditions.length ? " AND " + conditions.join(" AND ") : ""} GROUP BY identifier`
295
+ ).all(gene, ...params);
296
+ return new Map(rows.map((r) => [r.identifier, { modsite: r.modsite, isoform: r.isoform }]));
297
+ }
298
+ function queryDbRows(db, identifier, filters) {
299
+ const { conditions, params } = buildFilterClause(filters);
300
+ const allConditions = [`identifier = ? COLLATE NOCASE`, ...conditions];
368
301
  const sql = `SELECT organism, disease, identifier, protein_accession, isoform, modsite, gene, sample, value, brain_region
369
302
  FROM proteome_abundance
370
303
  WHERE ${allConditions.join(" AND ")}`;
371
- return db.prepare(sql).all(matchValue, ...params);
304
+ return db.prepare(sql).all(identifier, ...params);
372
305
  }
373
306
  async function getProteomeValuesFromCohort(ds, param, q) {
374
307
  const db = ds.queries.proteome.db;
375
- const hasBrainRegions = !!q.brainRegions;
376
308
  const { assay, cohort, organism } = param.dataTypeDetails;
377
309
  const organismConfig = q.organisms?.[organism];
378
310
  if (!organismConfig) throw `queries.proteome invalid organism: ${organism}`;
@@ -380,7 +312,6 @@ async function getProteomeValuesFromCohort(ds, param, q) {
380
312
  const organismColumnValue = organismConfig.columnValue;
381
313
  const assayConfig = organismConfig.assays?.[assay];
382
314
  if (!assayConfig) throw `queries.proteome.get invalid assay: ${assay}`;
383
- const PTMType = assayConfig.PTMType;
384
315
  const assayColumnIdx = assayConfig.columnIdx;
385
316
  const assayColumnValue = assayConfig.columnValue;
386
317
  const cohortConfig = assayConfig?.cohorts?.[cohort];
@@ -390,27 +321,16 @@ async function getProteomeValuesFromCohort(ds, param, q) {
390
321
  const organismFilter = [{ columnIdx: organismColumnIdx, columnValue: organismColumnValue }];
391
322
  const assayFilter = [{ columnIdx: assayColumnIdx, columnValue: assayColumnValue }];
392
323
  const term2sample2value = /* @__PURE__ */ new Map();
393
- const allEntries = [];
394
324
  const controlSampleIds = /* @__PURE__ */ new Set();
395
325
  for (const tw of param.terms) {
396
326
  if (!tw) continue;
397
327
  const fullGeneName = tw.term.name;
398
328
  const identifier = fullGeneName.split(":")[1]?.trim();
399
329
  const geneName = fullGeneName.split(":")[0]?.trim();
400
- if (param.for === "proteinView") {
401
- if (!geneName) throw "invalid term name for proteome query, gene name missing";
402
- } else {
403
- if (!identifier || !geneName)
404
- throw "invalid term name for proteome query, must be in format geneName: uniqueIdentifier";
405
- }
406
- const matchColumn = param.for === "proteinView" ? "gene" : "identifier";
407
- const matchValue = param.for === "proteinView" ? geneName : identifier;
408
- const caseRows = queryDbRows(db, matchColumn, matchValue, [...organismFilter, ...assayFilter, ...cohortCaseFilter]);
409
- const controlRows = queryDbRows(db, matchColumn, matchValue, [
410
- ...organismFilter,
411
- ...assayFilter,
412
- ...cohortControlFilter
413
- ]);
330
+ if (!identifier || !geneName)
331
+ throw "invalid term name for proteome query, must be in format geneName: uniqueIdentifier";
332
+ const caseRows = queryDbRows(db, identifier, [...organismFilter, ...assayFilter, ...cohortCaseFilter]);
333
+ const controlRows = queryDbRows(db, identifier, [...organismFilter, ...assayFilter, ...cohortControlFilter]);
414
334
  for (const row of controlRows) {
415
335
  const sid = ds.cohort.termdb.q.sampleName2id(row.sample);
416
336
  if (sid !== void 0) controlSampleIds.add(String(sid));
@@ -426,59 +346,18 @@ async function getProteomeValuesFromCohort(ds, param, q) {
426
346
  if (allowedSampleIds?.size == 0) {
427
347
  return { term2sample2value: /* @__PURE__ */ new Map(), byTermId: {}, bySampleId: {} };
428
348
  }
429
- if (param.for === "proteinView") {
430
- const entryMap = /* @__PURE__ */ new Map();
431
- for (const row of allRows) {
432
- const sid = ds.cohort.termdb.q.sampleName2id(row.sample);
433
- if (sid === void 0) continue;
434
- if (allowedSampleIds && !allowedSampleIds.has(sid)) continue;
435
- if (!entryMap.has(row.identifier)) {
436
- entryMap.set(row.identifier, {
437
- organism: row.organism,
438
- disease: row.disease,
439
- uniqueIdentifier: row.identifier,
440
- assayName: assay,
441
- cohortName: cohort,
442
- PTMType,
443
- modSites: PTMType ? row.modsite || void 0 : void 0,
444
- proteinAccession: row.protein_accession,
445
- isoform: row.isoform,
446
- // refSeq transcript ID mapped from protein_accession
447
- geneName: row.gene,
448
- s2v: {},
449
- // raw brain_region per sample id (only when brainRegions is configured);
450
- // remapped + finalized into entry.sampleRegions in the route's init().
451
- sampleRegionsRaw: hasBrainRegions ? {} : void 0
452
- });
453
- }
454
- entryMap.get(row.identifier).s2v[sid] = row.value;
455
- if (hasBrainRegions) entryMap.get(row.identifier).sampleRegionsRaw[sid] = row.brain_region;
456
- }
457
- for (const entry of entryMap.values()) allEntries.push(entry);
458
- } else {
459
- const s2v = {};
460
- for (const row of allRows) {
461
- const sid = ds.cohort.termdb.q.sampleName2id(row.sample);
462
- if (sid === void 0) continue;
463
- if (allowedSampleIds && !allowedSampleIds.has(sid)) continue;
464
- s2v[sid] = row.value;
465
- }
466
- if (Object.keys(s2v).length) {
467
- term2sample2value.set(tw.$id, s2v);
468
- }
469
- }
470
- }
471
- const bySampleId = {};
472
- if (param.for === "proteinView") {
473
- const sampleIds = /* @__PURE__ */ new Set();
474
- for (const entry of allEntries) {
475
- for (const sid of Object.keys(entry.s2v)) sampleIds.add(Number(sid));
349
+ const s2v = {};
350
+ for (const row of allRows) {
351
+ const sid = ds.cohort.termdb.q.sampleName2id(row.sample);
352
+ if (sid === void 0) continue;
353
+ if (allowedSampleIds && !allowedSampleIds.has(sid)) continue;
354
+ s2v[sid] = row.value;
476
355
  }
477
- for (const sid of sampleIds) {
478
- bySampleId[sid] = { label: ds.cohort.termdb.q.id2sampleName(sid) };
356
+ if (Object.keys(s2v).length) {
357
+ term2sample2value.set(tw.$id, s2v);
479
358
  }
480
- return { allEntries, controlSampleIds, bySampleId };
481
359
  }
360
+ const bySampleId = {};
482
361
  if (term2sample2value.size == 0) {
483
362
  throw `No data available for: ${param.terms?.map((t) => t.term.name).join(", ")}`;
484
363
  }
@@ -490,9 +369,9 @@ async function getProteomeValuesFromCohort(ds, param, q) {
490
369
  return { term2sample2value, controlSampleIds, bySampleId };
491
370
  }
492
371
  export {
493
- countDistinctSamples,
494
- getCohortStats,
495
372
  init,
373
+ listCohortSamples,
374
+ listIdentifierAnnotations,
496
375
  queryDbRows,
497
376
  validate_query_proteome
498
377
  };