@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.
- package/package.json +4 -4
- package/routes/termdb.chat.js +327 -186
- package/routes/termdb.runChart.js +50 -35
- package/src/app.js +453 -250
- package/src/mds3.gdc.filter.js +1 -1
|
@@ -14,9 +14,9 @@ const api = {
|
|
|
14
14
|
}
|
|
15
15
|
};
|
|
16
16
|
async function getRunChart(q, ds) {
|
|
17
|
-
const terms = [q.
|
|
18
|
-
const xTermId = q.
|
|
19
|
-
const yTermId = q.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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 =
|
|
202
|
+
y = 0;
|
|
180
203
|
}
|
|
181
204
|
y = Math.round(y * 100) / 100;
|
|
182
|
-
|
|
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
|
};
|