@sjcrh/proteinpaint-server 2.132.1-0 → 2.132.1-2

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.
@@ -7,7 +7,7 @@ function termdb_test_default() {
7
7
  isMds3: true,
8
8
  isSupportedChartOverride: {
9
9
  runChart: () => true,
10
- eventCount: () => true
10
+ frequencyChart: () => true
11
11
  },
12
12
  cohort: {
13
13
  massNav: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjcrh/proteinpaint-server",
3
- "version": "2.132.1-0",
3
+ "version": "2.132.1-2",
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
66
  "@sjcrh/proteinpaint-rust": "2.132.1-0",
67
- "@sjcrh/proteinpaint-shared": "2.132.0",
68
- "@sjcrh/proteinpaint-types": "2.132.1-0",
67
+ "@sjcrh/proteinpaint-shared": "2.132.1-2",
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: {
@@ -22,20 +22,54 @@ function parseJsonlOutput(rustOutput) {
22
22
  const allSuccessfulData = [];
23
23
  let finalSummary = null;
24
24
  let processedFiles = 0;
25
+ const MAX_RECORDS = 1e5;
26
+ let totalRecordsProcessed = 0;
27
+ let isCapReached = false;
25
28
  for (const line of lines) {
26
29
  const trimmedLine = line.trim();
27
30
  if (trimmedLine) {
28
31
  try {
29
32
  const data = JSON.parse(trimmedLine);
30
33
  if (data.type === "data") {
34
+ if (isCapReached) {
35
+ console.log(`[GRIN2] Skipping file ${data.case_id} - record cap of ${MAX_RECORDS} already reached`);
36
+ continue;
37
+ }
38
+ const remainingCapacity = MAX_RECORDS - totalRecordsProcessed;
39
+ const incomingRecords = data.data.length;
40
+ let recordsToProcess;
41
+ let recordsProcessedThisFile;
42
+ if (incomingRecords <= remainingCapacity) {
43
+ recordsToProcess = data.data;
44
+ recordsProcessedThisFile = incomingRecords;
45
+ } else {
46
+ recordsToProcess = data.data.slice(0, remainingCapacity);
47
+ recordsProcessedThisFile = remainingCapacity;
48
+ isCapReached = true;
49
+ console.log(
50
+ `[GRIN2] Record cap reached! Processing only ${recordsProcessedThisFile} of ${incomingRecords} records from file ${data.case_id}`
51
+ );
52
+ }
31
53
  processedFiles++;
32
- allSuccessfulData.push(data.data);
54
+ allSuccessfulData.push(recordsToProcess);
55
+ totalRecordsProcessed += recordsProcessedThisFile;
56
+ console.log(totalRecordsProcessed, MAX_RECORDS);
33
57
  console.log(
34
- `[GRIN2] Processed file ${processedFiles}: ${data.case_id} (${data.data_type}) - ${data.data.length} records`
58
+ `[GRIN2] Processed file ${processedFiles}: ${data.case_id} (${data.data_type}) - ${recordsProcessedThisFile} records`
35
59
  );
60
+ console.log(`[GRIN2] Total records processed: ${totalRecordsProcessed}/${MAX_RECORDS}`);
61
+ if (isCapReached) {
62
+ console.log(
63
+ `[GRIN2] RECORD CAP REACHED: ${MAX_RECORDS} records processed. Subsequent files will be skipped.`
64
+ );
65
+ }
36
66
  } else if (data.type === "summary") {
37
67
  finalSummary = data;
38
68
  console.log(`[GRIN2] Download complete: ${data.successful_files}/${data.total_files} files successful`);
69
+ if (isCapReached) {
70
+ console.log(`[GRIN2] Processing stopped due to record cap of ${MAX_RECORDS}`);
71
+ console.log(`[GRIN2] Total records collected: ${totalRecordsProcessed}`);
72
+ }
39
73
  if (data.failed_files > 0) {
40
74
  console.log(`[GRIN2] ${data.failed_files} files failed`);
41
75
  }
@@ -127,41 +161,50 @@ function init({ genomes }) {
127
161
  throw new Error("Failed to process MAF files: No result from Rust");
128
162
  }
129
163
  const parsedRustResult = rustResult;
130
- let dataForR = [];
164
+ let dataForPython = [];
131
165
  try {
132
166
  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`);
167
+ dataForPython = parsedRustResult.successful_data.flat();
168
+ console.log(`[GRIN2] Extracted ${dataForPython.length} records for python script`);
135
169
  console.log(
136
170
  `[GRIN2] Success: ${parsedRustResult.summary.successful_files}, Failed: ${parsedRustResult.summary.failed_files}`
137
171
  );
138
172
  } else {
139
173
  console.warn("[GRIN2] Unexpected Rust result format");
140
- dataForR = [];
174
+ dataForPython = [];
141
175
  }
142
176
  } catch (parseError) {
143
177
  console.error("[GRIN2] Error processing Rust result:", parseError);
144
- dataForR = [];
178
+ dataForPython = [];
145
179
  }
146
180
  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({
181
+ const pyInput = JSON.stringify({
149
182
  genedb: genedbfile,
150
183
  chromosomelist: g.majorchr,
151
- imagefile,
152
- lesion: dataForR
184
+ lesion: dataForPython
153
185
  // The mutation string from Rust
154
186
  });
155
- console.log("[GRIN2] Executing R script...");
187
+ console.log("[GRIN2] Executing python script...");
156
188
  const grin2AnalysisStart = Date.now();
157
- const rResult = await run_R("gdcGRIN2.R", rInput, []);
158
- console.log("[GRIN2] R execution completed");
189
+ let pyResult;
190
+ try {
191
+ pyResult = await run_python("gdcGRIN2.py", pyInput);
192
+ } catch (pyError) {
193
+ console.error("[GRIN2] Python execution failed:", pyError);
194
+ if (pyError && typeof pyError === "object" && "message" in pyError) {
195
+ throw new Error(`Python script failed: ${pyError.message}`);
196
+ } else {
197
+ throw new Error(`Python script failed: ${String(pyError)}`);
198
+ }
199
+ }
200
+ console.log("[GRIN2] python execution completed");
201
+ console.log(`[GRIN2] Python stderr: ${pyResult.stderr}`);
159
202
  const grin2AnalysisTime = formatElapsedTime(Date.now() - grin2AnalysisStart);
160
203
  console.log(`[GRIN2] Rust processing took ${grin2AnalysisTime}`);
161
204
  let resultData;
162
205
  try {
163
- resultData = JSON.parse(rResult);
164
- console.log("[GRIN2] Finished R analysis");
206
+ resultData = JSON.parse(pyResult);
207
+ console.log("[GRIN2] Finished python analysis");
165
208
  const pngImg = resultData.png[0];
166
209
  const topGeneTable = resultData.topGeneTable || null;
167
210
  const analysisStats = parsedRustResult.summary || {};
@@ -180,7 +223,7 @@ function init({ genomes }) {
180
223
  status: "success"
181
224
  });
182
225
  } catch (parseError) {
183
- console.error("[GRIN2] Error parsing R result:", parseError);
226
+ console.error("[GRIN2] Error parsing python result:", parseError);
184
227
  }
185
228
  } catch (e) {
186
229
  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
+ };