@sjcrh/proteinpaint-server 2.132.0 → 2.132.1-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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjcrh/proteinpaint-server",
3
- "version": "2.132.0",
3
+ "version": "2.132.1-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",
@@ -61,11 +61,11 @@
61
61
  },
62
62
  "dependencies": {
63
63
  "@sjcrh/augen": "2.121.0",
64
- "@sjcrh/proteinpaint-python": "2.118.0",
64
+ "@sjcrh/proteinpaint-python": "2.132.1-1",
65
65
  "@sjcrh/proteinpaint-r": "2.130.0",
66
- "@sjcrh/proteinpaint-rust": "2.132.0",
66
+ "@sjcrh/proteinpaint-rust": "2.132.1-0",
67
67
  "@sjcrh/proteinpaint-shared": "2.132.0",
68
- "@sjcrh/proteinpaint-types": "2.132.0",
68
+ "@sjcrh/proteinpaint-types": "2.132.1-1",
69
69
  "@types/express": "^5.0.0",
70
70
  "@types/express-session": "^1.18.1",
71
71
  "better-sqlite3": "^9.4.1",
@@ -1,9 +1,9 @@
1
1
  import { runGRIN2Payload } from "#types/checkers";
2
2
  import { stream_rust } from "@sjcrh/proteinpaint-rust";
3
- import { run_R } from "@sjcrh/proteinpaint-r";
4
3
  import serverconfig from "#src/serverconfig.js";
5
4
  import path from "path";
6
5
  import { formatElapsedTime } from "@sjcrh/proteinpaint-shared/time.js";
6
+ import { run_python } from "@sjcrh/proteinpaint-python";
7
7
  const api = {
8
8
  endpoint: "gdc/runGRIN2",
9
9
  methods: {
@@ -127,53 +127,69 @@ function init({ genomes }) {
127
127
  throw new Error("Failed to process MAF files: No result from Rust");
128
128
  }
129
129
  const parsedRustResult = rustResult;
130
- let dataForR = [];
130
+ let dataForPython = [];
131
131
  try {
132
132
  if (parsedRustResult.successful_data && Array.isArray(parsedRustResult.successful_data)) {
133
- dataForR = parsedRustResult.successful_data.flat();
134
- console.log(`[GRIN2] Extracted ${dataForR.length} records for R script`);
133
+ dataForPython = parsedRustResult.successful_data.flat();
134
+ console.log(`[GRIN2] Extracted ${dataForPython.length} records for python script`);
135
135
  console.log(
136
136
  `[GRIN2] Success: ${parsedRustResult.summary.successful_files}, Failed: ${parsedRustResult.summary.failed_files}`
137
137
  );
138
138
  } else {
139
139
  console.warn("[GRIN2] Unexpected Rust result format");
140
- dataForR = [];
140
+ dataForPython = [];
141
141
  }
142
142
  } catch (parseError) {
143
143
  console.error("[GRIN2] Error processing Rust result:", parseError);
144
- dataForR = [];
144
+ dataForPython = [];
145
145
  }
146
146
  const genedbfile = path.join(serverconfig.tpmasterdir, g.genedb.dbfile);
147
- const imagefile = path.join(serverconfig.cachedir, `grin2_${Date.now()}_${Math.floor(Math.random() * 1e9)}.png`);
148
- const rInput = JSON.stringify({
147
+ const pyInput = JSON.stringify({
149
148
  genedb: genedbfile,
150
149
  chromosomelist: g.majorchr,
151
- imagefile,
152
- lesion: dataForR
150
+ lesion: dataForPython
153
151
  // The mutation string from Rust
154
152
  });
155
- console.log("[GRIN2] Executing R script...");
156
- const rAnalysisTime = Date.now();
157
- const rResult = await run_R("gdcGRIN2.R", rInput, []);
158
- console.log("[GRIN2] R execution completed");
159
- console.log(`[GRIN2] R analysis took ${formatElapsedTime(Date.now() - rAnalysisTime)}`);
153
+ console.log("[GRIN2] Executing python script...");
154
+ const grin2AnalysisStart = Date.now();
155
+ let pyResult;
156
+ try {
157
+ pyResult = await run_python("gdcGRIN2.py", pyInput);
158
+ } catch (pyError) {
159
+ console.error("[GRIN2] Python execution failed:", pyError);
160
+ if (pyError && typeof pyError === "object" && "message" in pyError) {
161
+ throw new Error(`Python script failed: ${pyError.message}`);
162
+ } else {
163
+ throw new Error(`Python script failed: ${String(pyError)}`);
164
+ }
165
+ }
166
+ console.log("[GRIN2] python execution completed");
167
+ console.log(`[GRIN2] Python stderr: ${pyResult.stderr}`);
168
+ const grin2AnalysisTime = formatElapsedTime(Date.now() - grin2AnalysisStart);
169
+ console.log(`[GRIN2] Rust processing took ${grin2AnalysisTime}`);
160
170
  let resultData;
161
171
  try {
162
- resultData = JSON.parse(rResult);
163
- console.log("[GRIN2] Finished R analysis");
172
+ resultData = JSON.parse(pyResult);
173
+ console.log("[GRIN2] Finished python analysis");
164
174
  const pngImg = resultData.png[0];
165
175
  const topGeneTable = resultData.topGeneTable || null;
166
176
  const analysisStats = parsedRustResult.summary || {};
167
- console.log("[GRIN2] Total GRIN2 processing time:", formatElapsedTime(Date.now() - downloadStartTime));
177
+ const totalProcessTime = formatElapsedTime(Date.now() - downloadStartTime);
178
+ console.log("[GRIN2] Total GRIN2 processing time:", totalProcessTime);
168
179
  return res.json({
169
180
  pngImg,
170
181
  topGeneTable,
171
182
  rustResult: parsedRustResult,
172
183
  analysisStats,
184
+ timing: {
185
+ rustProcessingTime: downloadTime,
186
+ grin2ProcessingTime: grin2AnalysisTime,
187
+ totalTime: totalProcessTime
188
+ },
173
189
  status: "success"
174
190
  });
175
191
  } catch (parseError) {
176
- console.error("[GRIN2] Error parsing R result:", parseError);
192
+ console.error("[GRIN2] Error parsing python result:", parseError);
177
193
  }
178
194
  } catch (e) {
179
195
  console.error("[GRIN2] Error running analysis:", e);
@@ -0,0 +1,68 @@
1
+ import { ProfileFiltersPayload } from "#types/checkers";
2
+ import { getData, getSamplesPerFilter } from "../src/termdb.matrix.js";
3
+ const api = {
4
+ endpoint: "profileFilters",
5
+ methods: {
6
+ get: {
7
+ ...ProfileFiltersPayload,
8
+ init
9
+ },
10
+ post: {
11
+ ...ProfileFiltersPayload,
12
+ init
13
+ }
14
+ }
15
+ };
16
+ function init({ genomes }) {
17
+ return async (req, res) => {
18
+ try {
19
+ const g = genomes[req.query.genome];
20
+ if (!g)
21
+ throw "invalid genome name";
22
+ const ds = g.datasets?.[req.query.dslabel];
23
+ getFilters(req.query, ds, g, res);
24
+ } catch (e) {
25
+ console.log(e);
26
+ res.send({ status: "error", error: e.message || e });
27
+ }
28
+ };
29
+ }
30
+ function getList(samplesPerFilter, filtersData, tw) {
31
+ const values = Object.values(tw.term.values);
32
+ values.sort((v1, v2) => v1.label.localeCompare(v2.label));
33
+ const twSamples = samplesPerFilter[tw.term.id];
34
+ const data = [];
35
+ for (const sample of twSamples) {
36
+ data.push(filtersData.samples[sample]);
37
+ }
38
+ const sampleValues = Array.from(new Set(data.map((sample) => sample[tw.$id]?.value)));
39
+ for (const value of values) {
40
+ value.value = value.label;
41
+ value.disabled = !sampleValues.includes(value.label);
42
+ }
43
+ values.unshift({ label: "", value: "" });
44
+ return values;
45
+ }
46
+ async function getFilters(query, ds, genome, res) {
47
+ try {
48
+ const samplesPerFilter = await getSamplesPerFilter(query, ds);
49
+ const filtersData = await getData(
50
+ {
51
+ terms: query.terms
52
+ },
53
+ ds,
54
+ genome
55
+ );
56
+ const tw2List = {};
57
+ for (const tw of query.terms) {
58
+ tw2List[tw.term.id] = getList(samplesPerFilter, filtersData, tw);
59
+ }
60
+ res.send({ ...tw2List });
61
+ } catch (e) {
62
+ console.log(e);
63
+ res.send({ error: e.message || e });
64
+ }
65
+ }
66
+ export {
67
+ api
68
+ };
@@ -0,0 +1,117 @@
1
+ import { ProfileFormScoresPayload } from "#types/checkers";
2
+ import { getData } from "../src/termdb.matrix.js";
3
+ const api = {
4
+ endpoint: "profileFormScores",
5
+ methods: {
6
+ get: {
7
+ ...ProfileFormScoresPayload,
8
+ init
9
+ },
10
+ post: {
11
+ ...ProfileFormScoresPayload,
12
+ init
13
+ }
14
+ }
15
+ };
16
+ function init({ genomes }) {
17
+ return async (req, res) => {
18
+ try {
19
+ const g = genomes[req.query.genome];
20
+ if (!g)
21
+ throw "invalid genome name";
22
+ const ds = g.datasets?.[req.query.dslabel];
23
+ const result = await getScoresDict(req.query, ds, g);
24
+ res.send(result);
25
+ } catch (e) {
26
+ console.log(e);
27
+ res.send({ status: "error", error: e.message || e });
28
+ }
29
+ };
30
+ }
31
+ async function getScoresDict(query, ds, genome) {
32
+ const terms = [...query.scoreTerms];
33
+ if (query.scScoreTerms)
34
+ terms.push(...query.scScoreTerms);
35
+ const data = await getData(
36
+ {
37
+ terms,
38
+ filter: query.filter
39
+ //if isRadarFacility and site is specified, do not apply the filter
40
+ },
41
+ ds,
42
+ genome
43
+ );
44
+ const lst = Object.values(data.samples);
45
+ let sites = lst.map((s) => {
46
+ return { label: data.refs.bySampleId[s.sample].label, value: s.sample };
47
+ });
48
+ sites = lst.map((s) => {
49
+ return { label: data.refs.bySampleId[s.sample].label, value: s.sample };
50
+ });
51
+ if (query.userSites) {
52
+ sites = sites.filter((s) => query.userSites.includes(s.label));
53
+ }
54
+ let userSite;
55
+ if (query.userSites) {
56
+ const siteName = query.userSites[0];
57
+ userSite = ds.sampleName2Id.get(siteName);
58
+ if (!userSite) {
59
+ throw `Invalid user site: ${siteName}`;
60
+ }
61
+ }
62
+ const site = query.isAggregate ? query.site : userSite;
63
+ const sampleData = data.samples[site] || null;
64
+ const samples = Object.values(data.samples);
65
+ const term2Score = {};
66
+ for (const d of query.scoreTerms) {
67
+ const samples2 = sampleData ? [sampleData] : Object.values(data.samples);
68
+ const getDictFunc = (sample) => getDict(d.$id, sample);
69
+ const percents = getPercentsDict(getDictFunc, samples2);
70
+ term2Score[d.term.id] = percents;
71
+ }
72
+ if (query.scScoreTerms)
73
+ for (const d of query.scScoreTerms) {
74
+ const samples2 = sampleData ? [sampleData] : Object.values(data.samples);
75
+ const percents = getSCPercentsDict(d, samples2);
76
+ term2Score[d.term.id] = percents;
77
+ }
78
+ const hospital = sampleData?.[query.facilityTW.$id]?.value;
79
+ return { term2Score, sites, hospital, n: samples.length };
80
+ }
81
+ function getDict(key, sample) {
82
+ if (!sample[key])
83
+ return null;
84
+ const termData = sample[key].value;
85
+ return JSON.parse(termData);
86
+ }
87
+ function getPercentsDict(getDictFunc, samples) {
88
+ const percentageDict = {};
89
+ for (const sample of samples) {
90
+ const percents = getDictFunc(sample);
91
+ if (!percents)
92
+ continue;
93
+ for (const key in percents) {
94
+ const value = percents[key];
95
+ if (!percentageDict[key])
96
+ percentageDict[key] = 0;
97
+ percentageDict[key] += value;
98
+ }
99
+ }
100
+ return percentageDict;
101
+ }
102
+ function getSCPercentsDict(tw, samples) {
103
+ if (!tw)
104
+ throw "tw not defined";
105
+ const percentageDict = {};
106
+ for (const sample of samples) {
107
+ const twData = sample[tw.$id];
108
+ const key = twData?.value;
109
+ if (!percentageDict[key])
110
+ percentageDict[key] = 0;
111
+ percentageDict[key] += 1;
112
+ }
113
+ return percentageDict;
114
+ }
115
+ export {
116
+ api
117
+ };
@@ -0,0 +1,101 @@
1
+ import { ProfileScoresPayload } from "#types/checkers";
2
+ import { getData } from "../src/termdb.matrix.js";
3
+ const api = {
4
+ endpoint: "profileScores",
5
+ methods: {
6
+ get: {
7
+ ...ProfileScoresPayload,
8
+ init
9
+ },
10
+ post: {
11
+ ...ProfileScoresPayload,
12
+ init
13
+ }
14
+ }
15
+ };
16
+ function init({ genomes }) {
17
+ return async (req, res) => {
18
+ try {
19
+ const g = genomes[req.query.genome];
20
+ if (!g)
21
+ throw "invalid genome name";
22
+ const ds = g.datasets?.[req.query.dslabel];
23
+ const result = await getScores(req.query, ds, g);
24
+ res.send(result);
25
+ } catch (e) {
26
+ console.log(e);
27
+ res.send({ status: "error", error: e.message || e });
28
+ }
29
+ };
30
+ }
31
+ async function getScores(query, ds, genome) {
32
+ const isRadarFacility = query.isRadarFacility;
33
+ const terms = [query.facilityTW];
34
+ for (const term of query.scoreTerms) {
35
+ terms.push(term.score);
36
+ if (term.maxScore) {
37
+ terms.push(term.maxScore);
38
+ }
39
+ }
40
+ const data = await getData(
41
+ {
42
+ terms,
43
+ filter: isRadarFacility && query.site ? void 0 : query.filter
44
+ //if isRadarFacility and site is specified, do not apply the filter
45
+ },
46
+ ds,
47
+ genome
48
+ );
49
+ const lst = Object.values(data.samples);
50
+ let sites = lst.map((s) => {
51
+ return { label: data.refs.bySampleId[s.sample].label, value: s.sample };
52
+ });
53
+ sites = lst.map((s) => {
54
+ return { label: data.refs.bySampleId[s.sample].label, value: s.sample };
55
+ });
56
+ if (query.userSites) {
57
+ sites = sites.filter((s) => query.userSites.includes(s.label));
58
+ }
59
+ let userSite;
60
+ if (query.userSites) {
61
+ const siteName = query.userSites[0];
62
+ userSite = ds.sampleName2Id.get(siteName);
63
+ if (!userSite) {
64
+ throw `Invalid user site: ${siteName}`;
65
+ }
66
+ }
67
+ let site;
68
+ if (isRadarFacility)
69
+ site = query.site;
70
+ else
71
+ site = query.isAggregate ? query.site : userSite;
72
+ const sampleData = data.samples[site] || null;
73
+ const samples = Object.values(data.samples);
74
+ const term2Score = {};
75
+ for (const d of query.scoreTerms) {
76
+ term2Score[d.score.term.id] = getPercentage(d, samples, sampleData);
77
+ }
78
+ const hospital = sampleData?.[query.facilityTW.$id]?.value;
79
+ return { term2Score, sites, hospital, n: samples.length };
80
+ }
81
+ function getPercentage(d, samples, sampleData) {
82
+ if (!d)
83
+ return null;
84
+ const isAggregate = sampleData == null;
85
+ if (isAggregate) {
86
+ const maxScore = d.maxScore.term ? samples[0]?.[d.maxScore.$id]?.value : d.maxScore;
87
+ const scores = samples.map((sample) => sample[d.score.$id]?.value / maxScore * 100);
88
+ scores.sort((s1, s2) => s1 - s2);
89
+ const middle = Math.floor(scores.length / 2);
90
+ const score = scores.length % 2 !== 0 ? scores[middle] : (scores[middle - 1] + scores[middle]) / 2;
91
+ return Math.round(score);
92
+ } else {
93
+ const score = sampleData[d.score.$id]?.value;
94
+ const maxScore = d.maxScore.term ? sampleData[d.maxScore.$id]?.value : d.maxScore;
95
+ const percentage = score / maxScore * 100;
96
+ return Math.round(percentage);
97
+ }
98
+ }
99
+ export {
100
+ api
101
+ };
@@ -41,6 +41,7 @@ async function trigger_getcategories(q, res, tdb, ds, genome) {
41
41
  const $id = q.tw.$id;
42
42
  const arg = {
43
43
  filter: q.filter,
44
+ filter0: q.filter0,
44
45
  terms: [q.tw],
45
46
  currentGeneNames: q.currentGeneNames,
46
47
  // optional, from mds3 mayAddGetCategoryArgs()