@sjcrh/proteinpaint-server 2.174.1 → 2.176.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.
@@ -62,6 +62,7 @@ async function getResult(q, ds) {
62
62
  if (q.dataType == TermTypes.GENE_EXPRESSION) {
63
63
  _q = JSON.parse(JSON.stringify(q));
64
64
  _q.forClusteringAnalysis = true;
65
+ _q.__abortSignal = q.__abortSignal;
65
66
  }
66
67
  let term2sample2value, byTermId, bySampleId, skippedSexChrGenes;
67
68
  if (q.dataType == NUMERIC_DICTIONARY_TERM) {
@@ -80,7 +80,6 @@ function make(q, req, res, ds, genome) {
80
80
  if (ds.cohort.boxplots) c.boxplots = ds.cohort.boxplots;
81
81
  if (tdb.maxGeneVariantGeneSetSize) c.maxGeneVariantGeneSetSize = tdb.maxGeneVariantGeneSetSize;
82
82
  addRestrictAncestries(c, tdb);
83
- addScatterplots(c, ds);
84
83
  addMatrixplots(c, ds);
85
84
  addMutationSignatureplots(c, ds);
86
85
  addNonDictionaryQueries(c, ds, genome);
@@ -89,6 +88,7 @@ function make(q, req, res, ds, genome) {
89
88
  c.clientAuthResult = info?.clientAuthResult || {};
90
89
  if (tdb.displaySampleIds) c.displaySampleIds = tdb.displaySampleIds(c.clientAuthResult);
91
90
  c.authFilter = req.query.filter;
91
+ addScatterplots(c, ds, info);
92
92
  res.send({ termdbConfig: c });
93
93
  }
94
94
  function addRestrictAncestries(c, tdb) {
@@ -97,8 +97,12 @@ function addRestrictAncestries(c, tdb) {
97
97
  return { name: i.name, tvs: i.tvs, PCcount: i.PCcount };
98
98
  });
99
99
  }
100
- function addScatterplots(c, ds) {
100
+ function addScatterplots(c, ds, info) {
101
101
  if (!ds.cohort.scatterplots) return;
102
+ if (ds.cohort.scatterplots.get) {
103
+ c.scatterplots = ds.cohort.scatterplots.get(info?.clientAuthResult);
104
+ return;
105
+ }
102
106
  c.scatterplots = ds.cohort.scatterplots.plots.map((p) => {
103
107
  return {
104
108
  name: p.name,
@@ -37,14 +37,20 @@ function init({ genomes }) {
37
37
  }
38
38
  async function trigger_getDescrStats(q, ds) {
39
39
  const data = await getData(
40
- { filter: q.filter, filter0: q.filter0, terms: [q.tw], __protected__: q.__protected__ },
40
+ {
41
+ filter: q.filter,
42
+ filter0: q.filter0,
43
+ terms: [q.tw],
44
+ __protected__: q.__protected__,
45
+ __abortSignal: q.__abortSignal
46
+ },
41
47
  ds
42
48
  );
43
49
  if (data.error) throw data.error;
44
50
  const values = [];
45
51
  for (const key in data.samples) {
46
52
  const sample = data.samples[key];
47
- const v = sample[q.tw.$id];
53
+ const v = sample[q.tw.$id || ""];
48
54
  if (!v && v !== 0) {
49
55
  continue;
50
56
  }
@@ -14,9 +14,9 @@ const api = {
14
14
  }
15
15
  };
16
16
  async function getRunChart(q, ds) {
17
- const terms = [q.term, q.term2];
18
- const xTermId = q.term["$id"];
19
- const yTermId = q.term2["$id"];
17
+ const terms = [q.xtw, q.ytw];
18
+ const xTermId = q.xtw["$id"] ?? q.xtw.term?.id;
19
+ const yTermId = q.ytw["$id"] ?? q.ytw.term?.id;
20
20
  const { getData } = await import("../src/termdb.matrix.js");
21
21
  const data = await getData(
22
22
  {
@@ -28,20 +28,47 @@ async function getRunChart(q, ds) {
28
28
  true
29
29
  );
30
30
  if (data.error) throw new Error(data.error);
31
- return buildRunChartFromData(q.aggregation, xTermId, yTermId, data);
31
+ const shouldPartition = q.xtw?.q?.mode === "discrete";
32
+ return buildRunChartFromData(q.aggregation, xTermId, yTermId, data, shouldPartition, xTermId);
32
33
  }
33
- function buildRunChartFromData(aggregation, xTermId, yTermId, data) {
34
+ function buildRunChartFromData(aggregation, xTermId, yTermId, data, shouldPartition, partitionTermId) {
35
+ const allSamples = data.samples || {};
36
+ if (shouldPartition && partitionTermId) {
37
+ const period2Samples = {};
38
+ for (const sampleId in allSamples) {
39
+ const sample = allSamples[sampleId];
40
+ const partitionTerm = sample?.[partitionTermId];
41
+ if (partitionTerm?.key == null) {
42
+ continue;
43
+ }
44
+ const periodKey = partitionTerm.key;
45
+ if (!period2Samples[periodKey]) period2Samples[periodKey] = {};
46
+ period2Samples[periodKey][sampleId] = sample;
47
+ }
48
+ const periodKeys = Object.keys(period2Samples).sort();
49
+ const series = periodKeys.map((seriesId) => {
50
+ const subset = { samples: period2Samples[seriesId] };
51
+ const one2 = buildOneSeries(aggregation, xTermId, yTermId, subset);
52
+ return { seriesId, ...one2 };
53
+ });
54
+ return { status: "ok", series };
55
+ }
56
+ const one = buildOneSeries(aggregation, xTermId, yTermId, data);
57
+ return { status: "ok", series: [{ ...one }] };
58
+ }
59
+ function buildOneSeries(aggregation, xTermId, yTermId, data) {
60
+ const supportedAggregations = ["proportion", "count", "median"];
61
+ if (!supportedAggregations.includes(aggregation)) {
62
+ throw new Error(
63
+ `Unsupported aggregation method: ${aggregation}. Supported values are: ${supportedAggregations.join(", ")}`
64
+ );
65
+ }
34
66
  const buckets = {};
35
- let skippedSamples = 0;
36
67
  for (const sampleId in data.samples || {}) {
37
68
  const sample = data.samples[sampleId];
38
69
  const xRaw = sample?.[xTermId]?.value ?? sample?.[xTermId]?.key;
39
70
  const yRaw = sample?.[yTermId]?.value ?? sample?.[yTermId]?.key;
40
- if (xRaw == null || yRaw == null) {
41
- skippedSamples++;
42
- console.log(
43
- `Skipping sample ${sampleId}: Missing x or y value - xTermId=${xTermId} (value: ${xRaw}), yTermId=${yTermId} (value: ${yRaw})`
44
- );
71
+ if (xRaw == null) {
45
72
  continue;
46
73
  }
47
74
  if (typeof xRaw !== "number") {
@@ -68,13 +95,9 @@ function buildRunChartFromData(aggregation, xTermId, yTermId, data) {
68
95
  month = Math.floor(frac * 12) + 1;
69
96
  }
70
97
  } else {
71
- year = null;
98
+ month = 1;
72
99
  }
73
100
  if (year == null || month == null || Number.isNaN(year) || Number.isNaN(month)) {
74
- skippedSamples++;
75
- console.log(
76
- `Skipping sample ${sampleId}: Invalid date value - xTermId=${xTermId}, xRaw=${xRaw}, parsed year=${year}, month=${month}`
77
- );
78
101
  continue;
79
102
  }
80
103
  const yearNum = year;
@@ -87,8 +110,8 @@ function buildRunChartFromData(aggregation, xTermId, yTermId, data) {
87
110
  buckets[bucketKey] = {
88
111
  x,
89
112
  xName,
90
- ySum: 0,
91
113
  count: 0,
114
+ missingCount: 0,
92
115
  success: 0,
93
116
  total: 0,
94
117
  countSum: 0,
@@ -96,6 +119,10 @@ function buildRunChartFromData(aggregation, xTermId, yTermId, data) {
96
119
  yValues: []
97
120
  };
98
121
  }
122
+ if (yRaw == null) {
123
+ buckets[bucketKey].missingCount = (buckets[bucketKey].missingCount || 0) + 1;
124
+ continue;
125
+ }
99
126
  if (aggregation === "proportion") {
100
127
  if (typeof yRaw === "boolean") {
101
128
  buckets[bucketKey].success += yRaw ? 1 : 0;
@@ -142,17 +169,13 @@ function buildRunChartFromData(aggregation, xTermId, yTermId, data) {
142
169
  const yn = Number(yRaw);
143
170
  if (!Number.isFinite(yn)) {
144
171
  throw new Error(
145
- `Non-finite y value for mean aggregation in sample ${sampleId}: yTermId=${yTermId}, yRaw=${yRaw}`
172
+ `Non-finite y value for ${aggregation} aggregation in sample ${sampleId}: yTermId=${yTermId}, yRaw=${yRaw}`
146
173
  );
147
174
  }
148
- buckets[bucketKey].ySum += yn;
149
175
  buckets[bucketKey].count += 1;
150
176
  buckets[bucketKey].yValues.push(yn);
151
177
  }
152
178
  }
153
- if (skippedSamples > 0) {
154
- console.log(`buildRunChartFromData: Skipped ${skippedSamples} sample(s) due to missing x or y values`);
155
- }
156
179
  function xFromBucket(b) {
157
180
  const yearNum = Math.floor(b.sortKey / 100);
158
181
  const monthNum = b.sortKey % 100;
@@ -171,15 +194,16 @@ function buildRunChartFromData(aggregation, xTermId, yTermId, data) {
171
194
  return { x, xName: b.xName, y, sampleCount: b.count };
172
195
  } else {
173
196
  let y;
174
- if (aggregation === "median" && (b.yValues?.length ?? 0) > 0) {
197
+ if ((b.yValues?.length ?? 0) > 0) {
175
198
  const sorted = [...b.yValues].sort((a, b2) => a - b2);
176
199
  const mid = Math.floor(sorted.length / 2);
177
200
  y = sorted.length % 2 === 1 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
178
201
  } else {
179
- y = b.count ? b.ySum / b.count : 0;
202
+ y = 0;
180
203
  }
181
204
  y = Math.round(y * 100) / 100;
182
- return { x, xName: b.xName, y, sampleCount: b.count };
205
+ const sampleCount = b.count > 0 ? b.count : b.missingCount || 0;
206
+ return { x, xName: b.xName, y, sampleCount };
183
207
  }
184
208
  });
185
209
  const yValues = points.map((p) => p.y).filter((v) => typeof v === "number" && !Number.isNaN(v));
@@ -188,15 +212,7 @@ function buildRunChartFromData(aggregation, xTermId, yTermId, data) {
188
212
  const mid = Math.floor(sorted.length / 2);
189
213
  return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
190
214
  })() : 0;
191
- return {
192
- status: "ok",
193
- series: [
194
- {
195
- median,
196
- points
197
- }
198
- ]
199
- };
215
+ return { median, points };
200
216
  }
201
217
  function init({ genomes }) {
202
218
  return async (req, res) => {
@@ -209,7 +225,6 @@ function init({ genomes }) {
209
225
  const result = await getRunChart(q, ds);
210
226
  res.send(result);
211
227
  } catch (e) {
212
- console.log(e.stack);
213
228
  res.send({ error: e.message || e });
214
229
  }
215
230
  };
@@ -40,14 +40,14 @@ function init({ genomes }) {
40
40
  if (q.scaleDotTW) terms.push(q.scaleDotTW);
41
41
  if (q.coordTWs) for (const tw of q.coordTWs) terms.push(tw);
42
42
  const data = await getData(
43
- { filter: q.filter, filter0: q.filter0, terms, __protected__: q.__protected__ },
43
+ { filter: q.filter, filter0: q.filter0, terms, __protected__: q.__protected__, __abortSignal: q.__abortSignal },
44
44
  ds,
45
45
  true
46
46
  // FIXME 3rd arg hardcoded to true
47
47
  );
48
48
  if (data.error) throw new Error(data.error);
49
49
  let result;
50
- if (q.coordTWs.length > 0) {
50
+ if (q.coordTWs && q.coordTWs.length > 0) {
51
51
  const tmp = await getSampleCoordinatesByTerms(req, q, ds, data);
52
52
  cohortSamples = tmp[0];
53
53
  } else {
@@ -465,6 +465,8 @@ async function loadFile(p, ds) {
465
465
  }
466
466
  async function mayInitiateScatterplots(ds) {
467
467
  if (!ds.cohort.scatterplots) return;
468
+ if (typeof ds.cohort.scatterplots.get == "function") {
469
+ }
468
470
  if (!Array.isArray(ds.cohort.scatterplots.plots)) throw new Error("cohort.scatterplots.plots is not array");
469
471
  for (const p of ds.cohort.scatterplots.plots) {
470
472
  if (!p.name) throw new Error(".name missing from one of scatterplots.plots[]");
@@ -52,7 +52,8 @@ async function getViolin(q, ds) {
52
52
  filter: q.filter,
53
53
  filter0: q.filter0,
54
54
  currentGeneNames: q.currentGeneNames,
55
- __protected__: q.__protected__
55
+ __protected__: q.__protected__,
56
+ __abortSignal: q.__abortSignal
56
57
  },
57
58
  ds
58
59
  );
File without changes