@sjcrh/proteinpaint-server 2.174.1 → 2.175.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.
@@ -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
  };