@sjcrh/proteinpaint-server 2.188.1 → 2.190.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.
Files changed (82) hide show
  1. package/package.json +9 -12
  2. package/routes/aiProjectAdmin.js +2 -28
  3. package/routes/aiProjectSelectedWSImages.js +2 -17
  4. package/routes/brainImaging.js +1 -15
  5. package/routes/brainImagingSamples.js +1 -15
  6. package/routes/burden.js +1 -15
  7. package/routes/correlationVolcano.js +1 -15
  8. package/routes/dataset.js +1 -16
  9. package/routes/deleteWSITileSelection.js +2 -12
  10. package/routes/dsdata.js +1 -19
  11. package/routes/gdc.grin2.list.js +1 -15
  12. package/routes/gdc.grin2.run.js +1 -15
  13. package/routes/gdc.maf.js +1 -15
  14. package/routes/gdc.mafBuild.js +1 -15
  15. package/routes/genesetEnrichment.js +129 -97
  16. package/routes/grin2.js +110 -79
  17. package/routes/saveWSIAnnotation.js +2 -13
  18. package/routes/termdb.DE.js +137 -54
  19. package/routes/termdb.categories.js +2 -16
  20. package/routes/termdb.chat.js +169 -1076
  21. package/routes/termdb.cluster.js +5 -16
  22. package/routes/termdb.config.js +12 -17
  23. package/routes/termdb.descrstats.js +2 -16
  24. package/routes/termdb.diffMeth.js +100 -21
  25. package/routes/termdb.geneRanking.js +139 -0
  26. package/routes/termdb.proteome.js +1 -15
  27. package/routes/termdb.runChart.js +16 -30
  28. package/routes/termdb.sampleScatter.js +7 -97
  29. package/routes/termdb.singleCellPlots.js +159 -0
  30. package/routes/termdb.singlecellSamples.js +6 -16
  31. package/routes/termdb.violinBox.js +1 -15
  32. package/routes/wsimages.js +1 -16
  33. package/src/app.js +4014 -4127
  34. package/routes/_template_.js +0 -33
  35. package/routes/aiProjectTrainModel.js +0 -68
  36. package/routes/alphaGenome.js +0 -41
  37. package/routes/alphaGenomeTypes.js +0 -36
  38. package/routes/dzimages.js +0 -55
  39. package/routes/gene2canonicalisoform.js +0 -37
  40. package/routes/genelookup.js +0 -32
  41. package/routes/genesetOverrepresentation.js +0 -49
  42. package/routes/genomes.js +0 -150
  43. package/routes/healthcheck.js +0 -35
  44. package/routes/hicdata.js +0 -74
  45. package/routes/hicgenome.js +0 -75
  46. package/routes/hicstat.js +0 -35
  47. package/routes/img.js +0 -46
  48. package/routes/isoformlst.js +0 -48
  49. package/routes/ntseq.js +0 -36
  50. package/routes/pdomain.js +0 -53
  51. package/routes/profile.barchart2.js +0 -114
  52. package/routes/profile.forms2.js +0 -107
  53. package/routes/profile.polar2.js +0 -101
  54. package/routes/profile.radar2.js +0 -112
  55. package/routes/profile.radarFacility2.js +0 -148
  56. package/routes/sampledzimages.js +0 -48
  57. package/routes/samplewsimages.js +0 -60
  58. package/routes/snp.js +0 -98
  59. package/routes/termdb.chat2.js +0 -217
  60. package/routes/termdb.chat3.js +0 -209
  61. package/routes/termdb.cohort.summary.js +0 -37
  62. package/routes/termdb.cohorts.js +0 -41
  63. package/routes/termdb.dapVolcano.js +0 -80
  64. package/routes/termdb.dmr.js +0 -93
  65. package/routes/termdb.filterTermValues.js +0 -89
  66. package/routes/termdb.isoformAvailability.js +0 -35
  67. package/routes/termdb.numericcategories.js +0 -46
  68. package/routes/termdb.percentile.js +0 -66
  69. package/routes/termdb.profileFormScores.js +0 -92
  70. package/routes/termdb.profileScores.js +0 -113
  71. package/routes/termdb.rootterm.js +0 -39
  72. package/routes/termdb.sampleImages.js +0 -63
  73. package/routes/termdb.singleSampleMutation.js +0 -75
  74. package/routes/termdb.singlecellDEgenes.js +0 -55
  75. package/routes/termdb.singlecellData.js +0 -39
  76. package/routes/termdb.termchildren.js +0 -42
  77. package/routes/termdb.termsbyids.js +0 -50
  78. package/routes/termdb.topMutatedGenes.js +0 -127
  79. package/routes/termdb.topTermsByType.js +0 -96
  80. package/routes/termdb.topVariablyExpressedGenes.js +0 -132
  81. package/routes/tileserver.js +0 -68
  82. package/routes/wsisamples.js +0 -71
package/routes/hicstat.js DELETED
@@ -1,35 +0,0 @@
1
- import { fileurl, file_is_readable } from "#src/utils.js";
2
- import { do_hicstat } from "#src/hicstat.ts";
3
- import { hicstatPayload } from "#types/checkers";
4
- const api = {
5
- endpoint: "hicstat",
6
- methods: {
7
- get: {
8
- ...hicstatPayload,
9
- init
10
- },
11
- post: {
12
- ...hicstatPayload,
13
- init
14
- }
15
- }
16
- };
17
- function init() {
18
- return async (req, res) => {
19
- try {
20
- const [e, file, isurl] = fileurl(req);
21
- if (e) throw "illegal file name";
22
- if (!isurl) {
23
- await file_is_readable(file);
24
- }
25
- const out = await do_hicstat(file, isurl);
26
- res.send({ out });
27
- } catch (e) {
28
- res.send({ error: e instanceof Error ? e.message : e });
29
- if (e instanceof Error && e.stack) console.log(e);
30
- }
31
- };
32
- }
33
- export {
34
- api
35
- };
package/routes/img.js DELETED
@@ -1,46 +0,0 @@
1
- import { imgPayload } from "#types/checkers";
2
- import path from "path";
3
- import fs from "fs";
4
- import * as utils from "../src/utils.js";
5
- import { imageSize } from "image-size";
6
- const api = {
7
- endpoint: "img",
8
- methods: {
9
- get: {
10
- ...imgPayload,
11
- init
12
- },
13
- post: {
14
- ...imgPayload,
15
- init
16
- }
17
- }
18
- };
19
- function init() {
20
- return async (req, res) => {
21
- try {
22
- sendImage(req, res);
23
- } catch (e) {
24
- res.send({ status: "error", error: e.message || e });
25
- }
26
- };
27
- }
28
- async function sendImage(req, res) {
29
- const [e, file] = utils.fileurl(req, true);
30
- try {
31
- if (e) throw "invalid image file";
32
- const data = await fs.promises.readFile(file);
33
- const ext = path.extname(file).substring(1);
34
- const { width, height } = imageSize(file);
35
- const image = {
36
- src: `data:image/${ext};base64,${Buffer.from(data).toString("base64")}`,
37
- size: `${width}x${height}`
38
- };
39
- res.send({ ...image });
40
- } catch (e2) {
41
- res.send({ error: e2.message || e2 });
42
- }
43
- }
44
- export {
45
- api
46
- };
@@ -1,48 +0,0 @@
1
- import { isoformlstPayload } from "#types/checkers";
2
- const api = {
3
- // route endpoint
4
- // - no need for trailing slash
5
- // - should be a noun (method is based on HTTP GET, POST, etc)
6
- // - don't add 'Data' as response is assumed to be data
7
- endpoint: "isoformlst",
8
- methods: {
9
- get: {
10
- ...isoformlstPayload,
11
- init
12
- },
13
- post: {
14
- ...isoformlstPayload,
15
- init
16
- }
17
- }
18
- };
19
- function init({ genomes }) {
20
- return function handle_isoformlst(req, res) {
21
- try {
22
- const q = req.query;
23
- const g = genomes[q.genome];
24
- if (!g) throw "invalid genome";
25
- if (!Array.isArray(q.lst)) throw ".lst missing";
26
- const lst = [];
27
- for (const isoform of q.lst) {
28
- if (g.genomicNameRegexp.test(isoform)) continue;
29
- const tmp = g.genedb.getjsonbyisoform.all(isoform);
30
- lst.push(
31
- tmp.map((i) => {
32
- const j = JSON.parse(i.genemodel);
33
- if (i.isdefault) j.isdefault = true;
34
- return j;
35
- })
36
- );
37
- }
38
- res.send({ lst });
39
- } catch (e) {
40
- res.send({ error: e.message || e });
41
- if (e.stack) console.log(e.stack);
42
- else console.trace(e);
43
- }
44
- };
45
- }
46
- export {
47
- api
48
- };
package/routes/ntseq.js DELETED
@@ -1,36 +0,0 @@
1
- import { ntseqPayload } from "#types/checkers";
2
- import { get_fasta } from "#src/utils.js";
3
- const api = {
4
- endpoint: "ntseq",
5
- methods: {
6
- get: {
7
- ...ntseqPayload,
8
- init
9
- },
10
- post: {
11
- ...ntseqPayload,
12
- init
13
- }
14
- }
15
- };
16
- function init({ genomes }) {
17
- return async function handle_ntseq(req, res) {
18
- try {
19
- const q = req.query;
20
- if (!q.coord) throw "coord missing";
21
- const g = genomes[q.genome];
22
- if (!g) throw "invalid genome";
23
- if (!g.genomefile) throw "no sequence file available";
24
- const seq = await get_fasta(g, q.coord);
25
- res.send({
26
- seq: seq.split("\n").slice(1).join("")
27
- });
28
- } catch (e) {
29
- res.send({ error: e.message || e });
30
- if (e.stack) console.log(e.stack);
31
- }
32
- };
33
- }
34
- export {
35
- api
36
- };
package/routes/pdomain.js DELETED
@@ -1,53 +0,0 @@
1
- import { pdomainPayload } from "#types/checkers";
2
- const api = {
3
- // route endpoint
4
- // - no need for trailing slash
5
- // - should be a noun (method is based on HTTP GET, POST, etc)
6
- // - don't add 'Data' as response is assumed to be data
7
- endpoint: "pdomain",
8
- methods: {
9
- get: {
10
- ...pdomainPayload,
11
- init
12
- },
13
- post: {
14
- ...pdomainPayload,
15
- init
16
- }
17
- }
18
- };
19
- function init({ genomes }) {
20
- return function handle_pdomain(req, res) {
21
- try {
22
- const q = req.query;
23
- const gn = q.genome;
24
- if (!gn) throw "no genome";
25
- const g = genomes[gn];
26
- if (!g) throw "invalid genome " + gn;
27
- if (!g.proteindomain) {
28
- return res.send({ lst: [] });
29
- }
30
- if (!Array.isArray(q.isoforms)) throw "isoforms[] missing";
31
- const lst = [];
32
- for (const isoform of q.isoforms) {
33
- if (g.genomicNameRegexp.test(isoform)) continue;
34
- const tmp = g.proteindomain.getbyisoform.all(isoform);
35
- lst.push({
36
- name: isoform,
37
- pdomains: tmp.map((i) => {
38
- const j = JSON.parse(i.data);
39
- j.refseq = isoform;
40
- return j;
41
- })
42
- });
43
- }
44
- res.send({ lst });
45
- } catch (e) {
46
- res.send({ error: e.message || e });
47
- if (e.stack) console.log(e.stack);
48
- }
49
- };
50
- }
51
- export {
52
- api
53
- };
@@ -1,114 +0,0 @@
1
- import { ProfileScoresPayload } from "#types/checkers";
2
- import { getData } from "../src/termdb.matrix.js";
3
- const api = {
4
- endpoint: "termdb/profileBarchart2Scores",
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) throw "invalid genome name";
21
- const ds = g.datasets?.[req.query.dslabel];
22
- const result = await getScores(req.query, ds);
23
- res.send(result);
24
- } catch (e) {
25
- console.log(e);
26
- res.send({ status: "error", error: e.message || e });
27
- }
28
- };
29
- }
30
- function derivePrefix(query) {
31
- const firstScoreId = query.scoreTerms?.[0]?.score?.term?.id;
32
- if (firstScoreId?.startsWith("F")) return "F";
33
- if (firstScoreId?.startsWith("A")) return "A";
34
- for (const entry of query.filter?.lst || []) {
35
- const id = entry.tvs?.term?.id;
36
- if (id?.startsWith("F")) return "F";
37
- if (id?.startsWith("A")) return "A";
38
- }
39
- throw "cannot determine cohort prefix from scoreTerms or filter term IDs";
40
- }
41
- async function getScores(query, ds) {
42
- const { activeCohort, clientAuthResult } = query.__protected__;
43
- const prefix = derivePrefix(query);
44
- const facilityTermId = `${prefix}UNIT`;
45
- const facilityTW = { term: { id: facilityTermId }, q: {} };
46
- const terms = [facilityTW];
47
- for (const t of query.scoreTerms) {
48
- terms.push(t.score);
49
- if (t.maxScore?.term) terms.push(t.maxScore);
50
- }
51
- if (!query.filterByUserSites) {
52
- query.__protected__.ignoredTermIds.push(facilityTermId);
53
- }
54
- const cohortAuth = clientAuthResult[activeCohort];
55
- const isPublic = !cohortAuth?.role || cohortAuth.role === "public";
56
- const userSites = cohortAuth?.sites;
57
- const raw = await getData(
58
- {
59
- terms,
60
- filter: query.filter,
61
- __protected__: query.__protected__
62
- },
63
- ds
64
- );
65
- if (raw.error) throw raw.error;
66
- const sampleList = Object.values(raw.samples);
67
- let sites = sampleList.map((s) => {
68
- const val = s[facilityTW.$id].value;
69
- let label = facilityTW.term.values?.[val]?.label || val;
70
- if (label.length > 50) label = label.slice(0, 47) + "...";
71
- return { value: val, label };
72
- });
73
- if (userSites && query.filterByUserSites) {
74
- sites = sites.filter((s) => userSites.includes(s.value));
75
- }
76
- sites.sort((a, b) => a.label.localeCompare(b.label));
77
- const samples = Object.values(raw.samples);
78
- const eligibleSamples = userSites && query.filterByUserSites ? samples.filter((s) => userSites.includes(s[facilityTW.$id].value)) : samples;
79
- const term2Score = {};
80
- for (const d of query.scoreTerms) {
81
- const score = computeMedianPercentage(d, eligibleSamples);
82
- if (score !== null) term2Score[d.score.term.id] = score;
83
- }
84
- return {
85
- term2Score,
86
- // Public users see only aggregated scores — do not expose site IDs or names
87
- sites: isPublic ? [] : sites,
88
- n: eligibleSamples.length
89
- };
90
- }
91
- function computeMedianPercentage(d, samples) {
92
- const percentages = [];
93
- for (const s of samples) {
94
- const scoreValue = s[d.score.$id]?.value;
95
- if (scoreValue == null) continue;
96
- let maxScoreValue = null;
97
- if (typeof d.maxScore === "number") {
98
- maxScoreValue = d.maxScore;
99
- } else {
100
- maxScoreValue = s[d.maxScore.$id]?.value;
101
- }
102
- if (maxScoreValue == null || maxScoreValue === 0) continue;
103
- const percentage = scoreValue / maxScoreValue * 100;
104
- percentages.push(percentage);
105
- }
106
- if (percentages.length === 0) return null;
107
- percentages.sort((a, b) => a - b);
108
- const mid = Math.floor(percentages.length / 2);
109
- const median = percentages.length % 2 !== 0 ? percentages[mid] : (percentages[mid - 1] + percentages[mid]) / 2;
110
- return Math.round(median);
111
- }
112
- export {
113
- api
114
- };
@@ -1,107 +0,0 @@
1
- import { ProfileForms2ScoresPayload } from "#types/checkers";
2
- import { getData } from "../src/termdb.matrix.js";
3
- const api = {
4
- endpoint: "termdb/profileForms2Scores",
5
- methods: {
6
- get: {
7
- ...ProfileForms2ScoresPayload,
8
- init
9
- },
10
- post: {
11
- ...ProfileForms2ScoresPayload,
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) throw "invalid genome name";
21
- const ds = g.datasets?.[req.query.dslabel];
22
- const result = await getScores(req.query, ds);
23
- res.send(result);
24
- } catch (e) {
25
- console.log(e);
26
- res.send({ status: "error", error: e.message || e });
27
- }
28
- };
29
- }
30
- async function getScores(query, ds) {
31
- const { activeCohort, clientAuthResult } = query.__protected__;
32
- const facilityTermId = ds.cohort.termdb.plotConfigByCohort?.[activeCohort]?.profileForms2?.facilityTW?.id;
33
- if (!facilityTermId) throw `profileForms2.facilityTW.id missing for cohort '${activeCohort}'`;
34
- const facilityTW = { term: { id: facilityTermId }, q: {} };
35
- const terms = [facilityTW, ...query.scoreTerms];
36
- if (query.scScoreTerms) terms.push(...query.scScoreTerms);
37
- if (!query.filterByUserSites) query.__protected__.ignoredTermIds.push(facilityTermId);
38
- const cohortAuth = clientAuthResult[activeCohort];
39
- const isPublic = !cohortAuth?.role || cohortAuth.role === "public";
40
- const userSites = cohortAuth?.sites;
41
- const raw = await getData(
42
- {
43
- terms,
44
- filter: query.filter,
45
- __protected__: query.__protected__
46
- },
47
- ds
48
- );
49
- if (raw.error) throw raw.error;
50
- const samples = Object.values(raw.samples);
51
- let sites = samples.map((s) => {
52
- const val = s[facilityTW.$id].value;
53
- let label = facilityTW.term.values?.[val]?.label || val;
54
- if (label.length > 50) label = label.slice(0, 47) + "...";
55
- return { value: val, label };
56
- });
57
- if (userSites && query.filterByUserSites) sites = sites.filter((s) => userSites.includes(s.value));
58
- sites.sort((a, b) => a.label.localeCompare(b.label));
59
- const eligibleSamples = userSites && query.filterByUserSites ? samples.filter((s) => userSites.includes(s[facilityTW.$id].value)) : samples;
60
- const term2Score = {};
61
- for (const d of query.scoreTerms) {
62
- term2Score[d.term.id] = getPercentsDict((sample) => getDict(d.$id, sample), eligibleSamples);
63
- }
64
- if (query.scScoreTerms) {
65
- for (const d of query.scScoreTerms) {
66
- term2Score[d.term.id] = getSCPercentsDict(d, eligibleSamples);
67
- }
68
- }
69
- return {
70
- term2Score,
71
- sites: isPublic ? [] : sites,
72
- // public never sees site IDs
73
- n: eligibleSamples.length
74
- };
75
- }
76
- function getDict(key, sample) {
77
- if (!sample[key]) return null;
78
- const termData = sample[key].value;
79
- return JSON.parse(termData);
80
- }
81
- function getPercentsDict(getDictFunc, samples) {
82
- const percentageDict = {};
83
- for (const sample of samples) {
84
- const percents = getDictFunc(sample);
85
- if (!percents) continue;
86
- for (const key in percents) {
87
- const value = percents[key];
88
- if (!percentageDict[key]) percentageDict[key] = 0;
89
- percentageDict[key] += value;
90
- }
91
- }
92
- return percentageDict;
93
- }
94
- function getSCPercentsDict(tw, samples) {
95
- if (!tw) throw "tw not defined";
96
- const percentageDict = {};
97
- for (const sample of samples) {
98
- const key = sample[tw.$id]?.value;
99
- if (key == null) continue;
100
- if (!percentageDict[key]) percentageDict[key] = 0;
101
- percentageDict[key] += 1;
102
- }
103
- return percentageDict;
104
- }
105
- export {
106
- api
107
- };
@@ -1,101 +0,0 @@
1
- import { ProfileScoresPayload } from "#types/checkers";
2
- import { getData } from "../src/termdb.matrix.js";
3
- const api = {
4
- endpoint: "termdb/profilePolar2Scores",
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) throw "invalid genome name";
21
- const ds = g.datasets?.[req.query.dslabel];
22
- const result = await getScores(req.query, ds);
23
- res.send(result);
24
- } catch (e) {
25
- console.log(e);
26
- res.send({ status: "error", error: e.message || e });
27
- }
28
- };
29
- }
30
- function derivePrefix(query) {
31
- const firstScoreId = query.scoreTerms?.[0]?.score?.term?.id;
32
- if (firstScoreId?.startsWith("F")) return "F";
33
- if (firstScoreId?.startsWith("A")) return "A";
34
- for (const entry of query.filter?.lst || []) {
35
- const id = entry.tvs?.term?.id;
36
- if (id?.startsWith("F")) return "F";
37
- if (id?.startsWith("A")) return "A";
38
- }
39
- throw "cannot determine cohort prefix from scoreTerms or filter term IDs";
40
- }
41
- async function getScores(query, ds) {
42
- const { activeCohort, clientAuthResult } = query.__protected__;
43
- const prefix = derivePrefix(query);
44
- const facilityTermId = `${prefix}UNIT`;
45
- const facilityTW = { term: { id: facilityTermId }, q: {} };
46
- const terms = [facilityTW];
47
- for (const t of query.scoreTerms) {
48
- terms.push(t.score);
49
- if (t.maxScore?.term) terms.push(t.maxScore);
50
- }
51
- if (!query.filterByUserSites) {
52
- query.__protected__.ignoredTermIds.push(facilityTermId);
53
- }
54
- const cohortAuth = clientAuthResult[activeCohort];
55
- const isPublic = !cohortAuth?.role || cohortAuth.role === "public";
56
- const userSites = cohortAuth?.sites;
57
- const raw = await getData(
58
- {
59
- terms,
60
- filter: query.filter,
61
- __protected__: query.__protected__
62
- },
63
- ds
64
- );
65
- if (raw.error) throw raw.error;
66
- const sampleList = Object.values(raw.samples);
67
- let sites = sampleList.map((s) => {
68
- const val = s[facilityTW.$id].value;
69
- let label = facilityTW.term.values?.[val]?.label || val;
70
- if (label.length > 50) label = label.slice(0, 47) + "...";
71
- return { value: val, label };
72
- });
73
- if (userSites && query.filterByUserSites) {
74
- sites = sites.filter((s) => userSites.includes(s.value));
75
- }
76
- sites.sort((a, b) => a.label.localeCompare(b.label));
77
- const samples = Object.values(raw.samples);
78
- const eligibleSamples = userSites && query.filterByUserSites ? samples.filter((s) => userSites.includes(s[facilityTW.$id].value)) : samples;
79
- const term2Score = {};
80
- for (const d of query.scoreTerms) {
81
- const score = computeMedianPercentage(d, eligibleSamples);
82
- if (score !== null) term2Score[d.score.term.id] = score;
83
- }
84
- return {
85
- term2Score,
86
- // Public users see only aggregated scores — do not expose site IDs or names
87
- sites: isPublic ? [] : sites,
88
- n: eligibleSamples.length
89
- };
90
- }
91
- function computeMedianPercentage(d, samples) {
92
- const percentages = samples.filter((s) => s[d.score.$id]?.value != null).map((s) => s[d.score.$id].value / (s[d.maxScore.$id]?.value || d.maxScore) * 100);
93
- if (percentages.length === 0) return null;
94
- percentages.sort((a, b) => a - b);
95
- const mid = Math.floor(percentages.length / 2);
96
- const median = percentages.length % 2 !== 0 ? percentages[mid] : (percentages[mid - 1] + percentages[mid]) / 2;
97
- return Math.round(median);
98
- }
99
- export {
100
- api
101
- };
@@ -1,112 +0,0 @@
1
- import { ProfileScoresPayload } from "#types/checkers";
2
- import { getData } from "../src/termdb.matrix.js";
3
- const api = {
4
- endpoint: "termdb/profileRadar2Scores",
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) throw "invalid genome name";
21
- const ds = g.datasets?.[req.query.dslabel];
22
- const result = await getScores(req.query, ds);
23
- res.send(result);
24
- } catch (e) {
25
- console.log(e);
26
- res.send({ status: "error", error: e.message || e });
27
- }
28
- };
29
- }
30
- function derivePrefix(query) {
31
- const firstScoreId = query.scoreTerms?.[0]?.score?.term?.id;
32
- if (firstScoreId?.startsWith("F")) return "F";
33
- if (firstScoreId?.startsWith("A")) return "A";
34
- for (const entry of query.filter?.lst || []) {
35
- const id = entry.tvs?.term?.id;
36
- if (id?.startsWith("F")) return "F";
37
- if (id?.startsWith("A")) return "A";
38
- }
39
- throw "cannot determine cohort prefix from scoreTerms or filter term IDs";
40
- }
41
- async function getScores(query, ds) {
42
- const { activeCohort, clientAuthResult } = query.__protected__;
43
- const prefix = derivePrefix(query);
44
- const facilityTermId = `${prefix}UNIT`;
45
- const facilityTW = { term: { id: facilityTermId }, q: {} };
46
- const terms = [facilityTW];
47
- for (const t of query.scoreTerms) {
48
- terms.push(t.score);
49
- if (t.maxScore?.term) terms.push(t.maxScore);
50
- }
51
- if (!query.filterByUserSites) {
52
- query.__protected__.ignoredTermIds.push(facilityTermId);
53
- }
54
- const cohortAuth = clientAuthResult[activeCohort];
55
- const isPublic = !cohortAuth?.role || cohortAuth.role === "public";
56
- const userSites = cohortAuth?.sites;
57
- const raw = await getData(
58
- {
59
- terms,
60
- filter: query.filter,
61
- __protected__: query.__protected__
62
- },
63
- ds
64
- );
65
- if (raw.error) throw raw.error;
66
- const sampleList = Object.values(raw.samples);
67
- let sites = sampleList.map((s) => {
68
- const val = s[facilityTW.$id].value;
69
- let label = facilityTW.term.values?.[val]?.label || val;
70
- if (label.length > 50) label = label.slice(0, 47) + "...";
71
- return { value: val, label };
72
- });
73
- if (userSites && query.filterByUserSites) {
74
- sites = sites.filter((s) => userSites.includes(s.value));
75
- }
76
- sites.sort((a, b) => a.label.localeCompare(b.label));
77
- const samples = Object.values(raw.samples);
78
- const eligibleSamples = userSites && query.filterByUserSites ? samples.filter((s) => userSites.includes(s[facilityTW.$id].value)) : samples;
79
- const term2Score = {};
80
- for (const d of query.scoreTerms) {
81
- const score = computeMedianPercentage(d, eligibleSamples);
82
- if (score !== null) term2Score[d.score.term.id] = score;
83
- }
84
- return {
85
- term2Score,
86
- sites: isPublic ? [] : sites,
87
- n: eligibleSamples.length
88
- };
89
- }
90
- function computeMedianPercentage(d, samples) {
91
- const percentages = [];
92
- for (const s of samples) {
93
- const scoreValue = s[d.score.$id]?.value;
94
- if (scoreValue == null) continue;
95
- let maxScoreValue = null;
96
- if (typeof d.maxScore === "number") {
97
- maxScoreValue = d.maxScore;
98
- } else {
99
- maxScoreValue = s[d.maxScore.$id]?.value;
100
- }
101
- if (maxScoreValue == null || maxScoreValue === 0) continue;
102
- percentages.push(scoreValue / maxScoreValue * 100);
103
- }
104
- if (percentages.length === 0) return null;
105
- percentages.sort((a, b) => a - b);
106
- const mid = Math.floor(percentages.length / 2);
107
- const median = percentages.length % 2 !== 0 ? percentages[mid] : (percentages[mid - 1] + percentages[mid]) / 2;
108
- return Math.round(median);
109
- }
110
- export {
111
- api
112
- };