@sjcrh/proteinpaint-server 2.133.5 → 2.135.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.
@@ -7,7 +7,8 @@ function termdb_test_default() {
7
7
  isMds3: true,
8
8
  isSupportedChartOverride: {
9
9
  runChart: () => true,
10
- frequencyChart: () => true
10
+ frequencyChart: () => true,
11
+ report: () => true
11
12
  },
12
13
  cohort: {
13
14
  massNav: {
@@ -106,6 +107,76 @@ function termdb_test_default() {
106
107
  settings: {
107
108
  coxDisclaimer: "This is a test disclaimer for the cox regression analysis."
108
109
  }
110
+ },
111
+ plotConfigByCohort: {
112
+ default: {
113
+ report: {
114
+ sections: [
115
+ {
116
+ name: "Demographics",
117
+ plots: [
118
+ {
119
+ chartType: "barchart",
120
+ settings: { barchart: { colorBars: true } },
121
+ term: {
122
+ id: "agedx"
123
+ }
124
+ },
125
+ {
126
+ chartType: "barchart",
127
+ term: { id: "sex" },
128
+ settings: { barchart: { colorBars: true } }
129
+ },
130
+ {
131
+ chartType: "barchart",
132
+ term: { id: "genetic_race" },
133
+ settings: { barchart: { colorBars: true } }
134
+ }
135
+ ]
136
+ },
137
+ {
138
+ name: "Diagnosis",
139
+ plots: [
140
+ {
141
+ chartType: "barchart",
142
+ term: { id: "diaggrp" },
143
+ settings: { barchart: { colorBars: true } }
144
+ }
145
+ ]
146
+ },
147
+ {
148
+ name: "Treatment",
149
+ plots: [
150
+ {
151
+ chartType: "barchart",
152
+ term: { id: "hrtavg" },
153
+ settings: { barchart: { colorBars: true } }
154
+ },
155
+ {
156
+ chartType: "barchart",
157
+ term: { id: "aaclassic_5" },
158
+ settings: { barchart: { colorBars: true } }
159
+ }
160
+ ]
161
+ },
162
+ {
163
+ name: "Survival",
164
+ plots: [
165
+ {
166
+ chartType: "survival",
167
+ term: { id: "efs" },
168
+ term2: { id: "diaggrp" }
169
+ },
170
+ {
171
+ chartType: "survival",
172
+ term: { id: "os" },
173
+ term2: { id: "diaggrp" }
174
+ }
175
+ ]
176
+ }
177
+ ]
178
+ }
179
+ }
109
180
  }
110
181
  },
111
182
  scatterplots: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjcrh/proteinpaint-server",
3
- "version": "2.133.5",
3
+ "version": "2.135.0",
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.133.0",
64
+ "@sjcrh/proteinpaint-python": "2.135.0",
65
65
  "@sjcrh/proteinpaint-r": "2.130.0",
66
- "@sjcrh/proteinpaint-rust": "2.133.0",
67
- "@sjcrh/proteinpaint-shared": "2.133.5",
68
- "@sjcrh/proteinpaint-types": "2.133.5",
66
+ "@sjcrh/proteinpaint-rust": "2.135.0",
67
+ "@sjcrh/proteinpaint-shared": "2.135.0",
68
+ "@sjcrh/proteinpaint-types": "2.135.0",
69
69
  "@types/express": "^5.0.0",
70
70
  "@types/express-session": "^1.18.1",
71
71
  "better-sqlite3": "^9.4.1",
@@ -1,14 +1,14 @@
1
- import { ProfileFiltersPayload } from "#types/checkers";
1
+ import { FilterTermValuesPayload } from "#types/checkers";
2
2
  import { getData, getSamplesPerFilter } from "../src/termdb.matrix.js";
3
3
  const api = {
4
- endpoint: "profileFilters",
4
+ endpoint: "filterTermValues",
5
5
  methods: {
6
6
  get: {
7
- ...ProfileFiltersPayload,
7
+ ...FilterTermValuesPayload,
8
8
  init
9
9
  },
10
10
  post: {
11
- ...ProfileFiltersPayload,
11
+ ...FilterTermValuesPayload,
12
12
  init
13
13
  }
14
14
  }
@@ -38,9 +38,11 @@ function getList(samplesPerFilter, filtersData, tw) {
38
38
  const sampleValues = Array.from(new Set(data.map((sample) => sample[tw.$id]?.value)));
39
39
  for (const value of values) {
40
40
  value.value = value.label;
41
- value.disabled = !sampleValues.includes(value.label);
41
+ const label = value.label;
42
+ value.disabled = !sampleValues.includes(label);
42
43
  }
43
44
  values.unshift({ label: "", value: "" });
45
+ values.sort((a, b) => a.label.localeCompare(b.label));
44
46
  return values;
45
47
  }
46
48
  async function getFilters(query, ds, genome, res) {
@@ -3,6 +3,7 @@ import { stream_rust } from "@sjcrh/proteinpaint-rust";
3
3
  import serverconfig from "#src/serverconfig.js";
4
4
  import path from "path";
5
5
  import { run_python } from "@sjcrh/proteinpaint-python";
6
+ import { mayLog } from "#src/helpers.ts";
6
7
  const api = {
7
8
  endpoint: "gdc/runGRIN2",
8
9
  methods: {
@@ -16,6 +17,120 @@ const api = {
16
17
  }
17
18
  }
18
19
  };
20
+ function init({ genomes }) {
21
+ return async (req, res) => {
22
+ try {
23
+ await runGrin2(genomes, req, res);
24
+ } catch (e) {
25
+ console.error("[GRIN2] Error stack:", e.stack);
26
+ const errorResponse = {
27
+ status: "error",
28
+ error: e.message || String(e)
29
+ };
30
+ res.status(500).send(errorResponse);
31
+ }
32
+ };
33
+ }
34
+ async function runGrin2(genomes, req, res) {
35
+ const g = genomes.hg38;
36
+ if (!g)
37
+ throw "hg38 missing";
38
+ const ds = g.datasets.GDC;
39
+ if (!ds)
40
+ throw "hg38 GDC missing";
41
+ const parsedRequest = req.query;
42
+ const rustInput = {
43
+ caseFiles: parsedRequest.caseFiles,
44
+ mafOptions: parsedRequest.mafOptions,
45
+ cnvOptions: parsedRequest.cnvOptions,
46
+ chromosomes: []
47
+ };
48
+ const pyInput = {
49
+ genedb: path.join(serverconfig.tpmasterdir, g.genedb.dbfile),
50
+ chromosomelist: {},
51
+ lesion: []
52
+ };
53
+ for (const c in g.majorchr) {
54
+ if (ds.queries.singleSampleMutation?.discoPlot?.skipChrM) {
55
+ if (c.toLowerCase() == "chrm")
56
+ continue;
57
+ }
58
+ rustInput.chromosomes.push(c);
59
+ pyInput.chromosomelist[c] = g.majorchr[c];
60
+ }
61
+ let rustOutput = "";
62
+ let buffer = "";
63
+ const downloadStartTime = Date.now();
64
+ const streamResult = stream_rust("gdcGRIN2", JSON.stringify(rustInput), (errors) => {
65
+ if (errors) {
66
+ throw new Error(`Rust process failed: ${errors}`);
67
+ }
68
+ });
69
+ if (!streamResult) {
70
+ throw new Error("Failed to start Rust streaming process");
71
+ }
72
+ for await (const chunk of streamResult.rustStream) {
73
+ const chunkStr = chunk.toString();
74
+ rustOutput += chunkStr;
75
+ buffer += chunkStr;
76
+ const lines = buffer.split("\n");
77
+ buffer = lines.pop() || "";
78
+ for (const line of lines) {
79
+ const trimmedLine = line.trim();
80
+ if (trimmedLine) {
81
+ try {
82
+ const data = JSON.parse(trimmedLine);
83
+ if (data.type === "summary") {
84
+ mayLog(`[GRIN2] Download complete: ${data.successful_files}/${data.total_files} files successful`);
85
+ if (data.failed_files > 0) {
86
+ mayLog(`[GRIN2] ${data.failed_files} files failed`);
87
+ }
88
+ }
89
+ } catch (_parseError) {
90
+ }
91
+ }
92
+ }
93
+ }
94
+ mayLog("[GRIN2] Rust execution completed");
95
+ const downloadTime = Date.now() - downloadStartTime;
96
+ const downloadTimeToPrint = Math.round(downloadTime / 1e3);
97
+ mayLog(`[GRIN2] Rust processing took ${downloadTimeToPrint}`);
98
+ const rustResult = parseJsonlOutput(rustOutput);
99
+ if (!rustResult) {
100
+ throw new Error("Failed to process MAF files: No result from Rust");
101
+ }
102
+ const parsedRustResult = rustResult;
103
+ if (parsedRustResult.successful_data && Array.isArray(parsedRustResult.successful_data)) {
104
+ pyInput.lesion = parsedRustResult.successful_data.flat();
105
+ mayLog(`[GRIN2] Extracted ${pyInput.lesion.length} records for python script`);
106
+ mayLog(
107
+ `[GRIN2] Success: ${parsedRustResult.summary.successful_files}, Failed: ${parsedRustResult.summary.failed_files}`
108
+ );
109
+ } else {
110
+ throw "Unexpected Rust result format";
111
+ }
112
+ const grin2AnalysisStart = Date.now();
113
+ const pyResult = await run_python("gdcGRIN2.py", JSON.stringify(pyInput));
114
+ mayLog(`[GRIN2] Python stderr: ${pyResult.stderr}`);
115
+ const grin2AnalysisTime = Date.now() - grin2AnalysisStart;
116
+ const grin2AnalysisTimeToPrint = Math.round(grin2AnalysisTime / 1e3);
117
+ mayLog(`[GRIN2] Python processing took ${grin2AnalysisTimeToPrint}`);
118
+ const resultData = JSON.parse(pyResult);
119
+ const pngImg = resultData.png[0];
120
+ const topGeneTable = resultData.topGeneTable || null;
121
+ const totalProcessTime = downloadTimeToPrint + grin2AnalysisTimeToPrint;
122
+ return res.json({
123
+ pngImg,
124
+ topGeneTable,
125
+ rustResult: parsedRustResult,
126
+ timing: {
127
+ rustProcessingTime: downloadTimeToPrint,
128
+ grin2ProcessingTime: grin2AnalysisTimeToPrint,
129
+ totalTime: totalProcessTime
130
+ },
131
+ status: "success"
132
+ });
133
+ }
19
134
  function parseJsonlOutput(rustOutput) {
20
135
  const lines = rustOutput.trim().split("\n");
21
136
  const allSuccessfulData = [];
@@ -31,7 +146,7 @@ function parseJsonlOutput(rustOutput) {
31
146
  const data = JSON.parse(trimmedLine);
32
147
  if (data.type === "data") {
33
148
  if (isCapReached) {
34
- console.log(`[GRIN2] Skipping file ${data.case_id} - record cap of ${MAX_RECORDS} already reached`);
149
+ mayLog(`[GRIN2] Skipping file ${data.case_id} - record cap of ${MAX_RECORDS} already reached`);
35
150
  continue;
36
151
  }
37
152
  const remainingCapacity = MAX_RECORDS - totalRecordsProcessed;
@@ -45,32 +160,29 @@ function parseJsonlOutput(rustOutput) {
45
160
  recordsToProcess = data.data.slice(0, remainingCapacity);
46
161
  recordsProcessedThisFile = remainingCapacity;
47
162
  isCapReached = true;
48
- console.log(
163
+ mayLog(
49
164
  `[GRIN2] Record cap reached! Processing only ${recordsProcessedThisFile} of ${incomingRecords} records from file ${data.case_id}`
50
165
  );
51
166
  }
52
167
  processedFiles++;
53
168
  allSuccessfulData.push(recordsToProcess);
54
169
  totalRecordsProcessed += recordsProcessedThisFile;
55
- console.log(totalRecordsProcessed, MAX_RECORDS);
56
- console.log(
170
+ mayLog(
57
171
  `[GRIN2] Processed file ${processedFiles}: ${data.case_id} (${data.data_type}) - ${recordsProcessedThisFile} records`
58
172
  );
59
- console.log(`[GRIN2] Total records processed: ${totalRecordsProcessed}/${MAX_RECORDS}`);
173
+ mayLog(`[GRIN2] Total records processed: ${totalRecordsProcessed}/${MAX_RECORDS}`);
60
174
  if (isCapReached) {
61
- console.log(
62
- `[GRIN2] RECORD CAP REACHED: ${MAX_RECORDS} records processed. Subsequent files will be skipped.`
63
- );
175
+ mayLog(`[GRIN2] RECORD CAP REACHED: ${MAX_RECORDS} records processed. Subsequent files will be skipped.`);
64
176
  }
65
177
  } else if (data.type === "summary") {
66
178
  finalSummary = data;
67
- console.log(`[GRIN2] Download complete: ${data.successful_files}/${data.total_files} files successful`);
179
+ mayLog(`[GRIN2] Download complete: ${data.successful_files}/${data.total_files} files successful`);
68
180
  if (isCapReached) {
69
- console.log(`[GRIN2] Processing stopped due to record cap of ${MAX_RECORDS}`);
70
- console.log(`[GRIN2] Total records collected: ${totalRecordsProcessed}`);
181
+ mayLog(`[GRIN2] Processing stopped due to record cap of ${MAX_RECORDS}`);
182
+ mayLog(`[GRIN2] Total records collected: ${totalRecordsProcessed}`);
71
183
  }
72
184
  if (data.failed_files > 0) {
73
- console.log(`[GRIN2] ${data.failed_files} files failed`);
185
+ mayLog(`[GRIN2] ${data.failed_files} files failed`);
74
186
  }
75
187
  }
76
188
  } catch (parseError) {
@@ -86,6 +198,7 @@ function parseJsonlOutput(rustOutput) {
86
198
  successful_data: allSuccessfulData,
87
199
  failed_files: finalSummary.errors || [],
88
200
  summary: {
201
+ type: "summary",
89
202
  total_files: finalSummary.total_files,
90
203
  successful_files: finalSummary.successful_files,
91
204
  failed_files: finalSummary.failed_files,
@@ -93,148 +206,11 @@ function parseJsonlOutput(rustOutput) {
93
206
  filtered_records: finalSummary.filtered_records || 0,
94
207
  filtered_maf_records: finalSummary.filtered_maf_records || 0,
95
208
  filtered_cnv_records: finalSummary.filtered_cnv_records || 0,
96
- filtered_records_by_case: finalSummary.filtered_records_by_case || {},
97
209
  included_maf_records: finalSummary.included_maf_records || 0,
98
- included_cnv_records: finalSummary.included_cnv_records || 0
99
- }
100
- };
101
- }
102
- function init({ genomes }) {
103
- return async (req, res) => {
104
- try {
105
- console.log("[GRIN2] Validating genome configuration");
106
- const g = genomes.hg38;
107
- if (!g)
108
- throw "hg38 missing";
109
- const ds = g.datasets.GDC;
110
- if (!ds)
111
- throw "hg38 GDC missing";
112
- console.log(`[GRIN2] Request received:`, JSON.stringify(req.query));
113
- const parsedRequest = req.query;
114
- console.log(`[GRIN2] Parsed request: ${JSON.stringify(parsedRequest)}`);
115
- const rustInput = JSON.stringify({
116
- caseFiles: parsedRequest.caseFiles,
117
- mafOptions: parsedRequest.mafOptions,
118
- cnvOptions: parsedRequest.cnvOptions
119
- });
120
- console.log(`[GRIN2] Rust input: ${rustInput}`);
121
- console.log("[GRIN2] Executing Rust function with streaming...");
122
- let rustOutput = "";
123
- let buffer = "";
124
- const downloadStartTime = Date.now();
125
- const streamResult = stream_rust("gdcGRIN2", rustInput, (errors) => {
126
- if (errors) {
127
- throw new Error(`Rust process failed: ${errors}`);
128
- }
129
- });
130
- if (!streamResult) {
131
- throw new Error("Failed to start Rust streaming process");
132
- }
133
- for await (const chunk of streamResult.rustStream) {
134
- const chunkStr = chunk.toString();
135
- rustOutput += chunkStr;
136
- buffer += chunkStr;
137
- const lines = buffer.split("\n");
138
- buffer = lines.pop() || "";
139
- for (const line of lines) {
140
- const trimmedLine = line.trim();
141
- if (trimmedLine) {
142
- try {
143
- const data = JSON.parse(trimmedLine);
144
- if (data.type === "summary") {
145
- console.log(`[GRIN2] Download complete: ${data.successful_files}/${data.total_files} files successful`);
146
- if (data.failed_files > 0) {
147
- console.log(`[GRIN2] ${data.failed_files} files failed`);
148
- }
149
- }
150
- } catch (_parseError) {
151
- }
152
- }
153
- }
154
- }
155
- console.log("[GRIN2] Rust execution completed");
156
- const downloadTime = Date.now() - downloadStartTime;
157
- const downloadTimeToPrint = Math.round(downloadTime / 1e3);
158
- console.log(`[GRIN2] Rust processing took ${downloadTimeToPrint}`);
159
- const rustResult = parseJsonlOutput(rustOutput);
160
- if (!rustResult) {
161
- throw new Error("Failed to process MAF files: No result from Rust");
162
- }
163
- const parsedRustResult = rustResult;
164
- let dataForPython = [];
165
- try {
166
- if (parsedRustResult.successful_data && Array.isArray(parsedRustResult.successful_data)) {
167
- dataForPython = parsedRustResult.successful_data.flat();
168
- console.log(`[GRIN2] Extracted ${dataForPython.length} records for python script`);
169
- console.log(
170
- `[GRIN2] Success: ${parsedRustResult.summary.successful_files}, Failed: ${parsedRustResult.summary.failed_files}`
171
- );
172
- } else {
173
- console.warn("[GRIN2] Unexpected Rust result format");
174
- dataForPython = [];
175
- }
176
- } catch (parseError) {
177
- console.error("[GRIN2] Error processing Rust result:", parseError);
178
- dataForPython = [];
179
- }
180
- const genedbfile = path.join(serverconfig.tpmasterdir, g.genedb.dbfile);
181
- const pyInput = JSON.stringify({
182
- genedb: genedbfile,
183
- chromosomelist: g.majorchr,
184
- lesion: dataForPython
185
- // The mutation string from Rust
186
- });
187
- console.log("[GRIN2] Executing python script...");
188
- const grin2AnalysisStart = Date.now();
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}`);
202
- const grin2AnalysisTime = Date.now() - grin2AnalysisStart;
203
- const grin2AnalysisTimeToPrint = Math.round(grin2AnalysisTime / 1e3);
204
- console.log(`[GRIN2] Python processing took ${grin2AnalysisTimeToPrint}`);
205
- let resultData;
206
- try {
207
- resultData = JSON.parse(pyResult);
208
- console.log("[GRIN2] Finished python analysis");
209
- const pngImg = resultData.png[0];
210
- const topGeneTable = resultData.topGeneTable || null;
211
- const analysisStats = parsedRustResult.summary || {};
212
- const totalProcessTime = downloadTimeToPrint + grin2AnalysisTimeToPrint;
213
- console.log("[GRIN2] Total GRIN2 processing time:", totalProcessTime);
214
- return res.json({
215
- pngImg,
216
- topGeneTable,
217
- rustResult: parsedRustResult,
218
- analysisStats,
219
- timing: {
220
- rustProcessingTime: downloadTimeToPrint,
221
- grin2ProcessingTime: grin2AnalysisTimeToPrint,
222
- totalTime: totalProcessTime
223
- },
224
- status: "success"
225
- });
226
- } catch (parseError) {
227
- console.error("[GRIN2] Error parsing python result:", parseError);
228
- }
229
- } catch (e) {
230
- console.error("[GRIN2] Error running analysis:", e);
231
- console.error("[GRIN2] Error stack:", e.stack);
232
- const errorResponse = {
233
- status: "error",
234
- error: e.message || String(e)
235
- };
236
- console.log(`[GRIN2] Sending error response: ${JSON.stringify(errorResponse)}`);
237
- res.status(500).send(errorResponse);
210
+ included_cnv_records: finalSummary.included_cnv_records || 0,
211
+ filtered_records_by_case: finalSummary.filtered_records_by_case || {},
212
+ hyper_mutator_records: finalSummary.hyper_mutator_records || {},
213
+ skippedChromosomes: finalSummary.skipped_chromosomes || {}
238
214
  }
239
215
  };
240
216
  }
@@ -35,7 +35,7 @@ async function getScoresDict(query, ds, genome) {
35
35
  const data = await getData(
36
36
  {
37
37
  terms,
38
- filter: query.filter
38
+ filter: query.site || !query.isAggregate ? void 0 : query.filter
39
39
  //if isRadarFacility and site is specified, do not apply the filter
40
40
  },
41
41
  ds,
@@ -48,19 +48,14 @@ async function getScoresDict(query, ds, genome) {
48
48
  sites = lst.map((s) => {
49
49
  return { label: data.refs.bySampleId[s.sample].label, value: s.sample };
50
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;
51
+ let sitesSelected = [];
52
+ if (query.site)
53
+ sitesSelected = [query.site];
54
+ else if (!query.isAggregate)
55
+ sitesSelected = [sites[0].value];
56
+ else
57
+ sitesSelected = query.sites;
58
+ const sampleData = sitesSelected?.length == 1 ? data.samples[sitesSelected[0]] : null;
64
59
  const samples = Object.values(data.samples);
65
60
  const term2Score = {};
66
61
  for (const d of query.scoreTerms) {
@@ -76,7 +71,7 @@ async function getScoresDict(query, ds, genome) {
76
71
  term2Score[d.term.id] = percents;
77
72
  }
78
73
  const hospital = sampleData?.[query.facilityTW.$id]?.value;
79
- return { term2Score, sites, hospital, n: samples.length };
74
+ return { term2Score, sites, hospital, n: sampleData ? 1 : samples.length };
80
75
  }
81
76
  function getDict(key, sample) {
82
77
  if (!sample[key])
@@ -29,7 +29,6 @@ function init({ genomes }) {
29
29
  };
30
30
  }
31
31
  async function getScores(query, ds, genome) {
32
- const isRadarFacility = query.isRadarFacility;
33
32
  const terms = [query.facilityTW];
34
33
  for (const term of query.scoreTerms) {
35
34
  terms.push(term.score);
@@ -40,8 +39,8 @@ async function getScores(query, ds, genome) {
40
39
  const data = await getData(
41
40
  {
42
41
  terms,
43
- filter: isRadarFacility && query.site ? void 0 : query.filter
44
- //if isRadarFacility and site is specified, do not apply the filter
42
+ filter: query.site || !query.isAggregate ? void 0 : query.filter
43
+ //if site is specified, do not apply the filter that is for the aggregation
45
44
  },
46
45
  ds,
47
46
  genome
@@ -56,27 +55,23 @@ async function getScores(query, ds, genome) {
56
55
  if (query.userSites) {
57
56
  sites = sites.filter((s) => query.userSites.includes(s.label));
58
57
  }
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;
58
+ let sitesSelected = [];
59
+ if (query.site)
60
+ sitesSelected = [query.site];
61
+ else if (!query.isAggregate)
62
+ sitesSelected = [sites[0].value];
70
63
  else
71
- site = query.isAggregate ? query.site : userSite;
72
- const sampleData = data.samples[site] || null;
73
- const samples = Object.values(data.samples);
64
+ sitesSelected = query.sites;
65
+ const sampleData = sitesSelected?.length == 1 ? data.samples[sitesSelected[0]] : null;
66
+ let samples = Object.values(data.samples);
67
+ if (sitesSelected?.length > 0)
68
+ samples = samples.filter((s) => sitesSelected.includes(s.sample));
74
69
  const term2Score = {};
75
70
  for (const d of query.scoreTerms) {
76
71
  term2Score[d.score.term.id] = getPercentage(d, samples, sampleData);
77
72
  }
78
73
  const hospital = sampleData?.[query.facilityTW.$id]?.value;
79
- return { term2Score, sites, hospital, n: samples.length };
74
+ return { term2Score, sites, hospital, n: sampleData ? 1 : samples.length };
80
75
  }
81
76
  function getPercentage(d, samples, sampleData) {
82
77
  if (!d)