@sjcrh/proteinpaint-server 2.197.0 → 2.199.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.
@@ -389,7 +389,6 @@ function termdb_test_default() {
389
389
  }
390
390
  },
391
391
  geneExpression: {
392
- src: "native",
393
392
  file: "files/hg38/TermdbTest/rnaseq/TermdbTest.fpkm.matrix.new.h5",
394
393
  unit: "FPKM"
395
394
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjcrh/proteinpaint-server",
3
- "version": "2.197.0",
3
+ "version": "2.199.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",
@@ -36,7 +36,6 @@
36
36
  "license": "SEE LICENSE IN ./LICENSE",
37
37
  "devDependencies": {
38
38
  "@babel/core": "^7.9.6",
39
- "@babel/plugin-proposal-optional-chaining": "^7.9.0",
40
39
  "@babel/plugin-transform-runtime": "^7.14.5",
41
40
  "@babel/preset-env": "^7.9.6",
42
41
  "@babel/preset-typescript": "^7.21.4",
@@ -59,11 +58,11 @@
59
58
  },
60
59
  "dependencies": {
61
60
  "@sjcrh/augen": "2.190.0",
62
- "@sjcrh/proteinpaint-python": "2.196.0",
63
- "@sjcrh/proteinpaint-r": "2.197.0",
64
- "@sjcrh/proteinpaint-rust": "2.197.0",
65
- "@sjcrh/proteinpaint-shared": "2.197.0",
66
- "@sjcrh/proteinpaint-types": "2.197.0",
61
+ "@sjcrh/proteinpaint-python": "2.199.0",
62
+ "@sjcrh/proteinpaint-r": "2.199.0",
63
+ "@sjcrh/proteinpaint-rust": "2.198.0",
64
+ "@sjcrh/proteinpaint-shared": "2.199.0",
65
+ "@sjcrh/proteinpaint-types": "2.199.0",
67
66
  "@types/express": "^5.0.0",
68
67
  "@types/express-session": "^1.18.1",
69
68
  "better-sqlite3": "^12.4.1",
@@ -103,7 +102,6 @@
103
102
  "start.js",
104
103
  "src/app.js",
105
104
  "src/serverconfig.js",
106
- "src/mds3.gdc.filter.js",
107
105
  "src/checkReadingFrame.js",
108
106
  "src/bedj.parseBed.js",
109
107
  "LICENSE/*"
@@ -0,0 +1,176 @@
1
+ import { getData } from "../src/termdb.matrix.js";
2
+ const api = {
3
+ endpoint: "termdb/profileImpressionDistribution",
4
+ methods: {
5
+ get: {
6
+ init,
7
+ request: { typeId: "ProfileImpressionDistributionRequest" },
8
+ response: { typeId: "ProfileImpressionDistributionResponse" }
9
+ },
10
+ post: {
11
+ init,
12
+ request: { typeId: "ProfileImpressionDistributionRequest" },
13
+ response: { typeId: "ProfileImpressionDistributionResponse" }
14
+ }
15
+ }
16
+ };
17
+ function init({ genomes }) {
18
+ return async (req, res) => {
19
+ try {
20
+ const g = genomes[req.query.genome];
21
+ if (!g) throw "invalid genome name";
22
+ const ds = g.datasets?.[req.query.dslabel];
23
+ const result = await getDistribution(req.query, ds);
24
+ res.send(result);
25
+ } catch (e) {
26
+ console.log(e);
27
+ res.send({ status: "error", error: e.message || e });
28
+ }
29
+ };
30
+ }
31
+ function derivePrefix(query) {
32
+ const id = query.scTermId || query.pocTermId;
33
+ if (id?.startsWith("F")) return "F";
34
+ if (id?.startsWith("A")) return "A";
35
+ for (const entry of query.filter?.lst || []) {
36
+ const fid = entry.tvs?.term?.id;
37
+ if (fid?.startsWith("F")) return "F";
38
+ if (fid?.startsWith("A")) return "A";
39
+ }
40
+ throw "cannot determine cohort prefix from request term IDs";
41
+ }
42
+ async function getDistribution(query, ds) {
43
+ const { activeCohort, clientAuthResult } = query.__protected__;
44
+ const prefix = derivePrefix(query);
45
+ const facilityTermId = `${prefix}UNIT`;
46
+ const facilityTW = { term: { id: facilityTermId }, q: {} };
47
+ const scTW = { term: { id: query.scTermId }, q: { mode: "continuous" } };
48
+ const pocTW = query.pocTermId ? { term: { id: query.pocTermId }, q: { mode: "continuous" } } : null;
49
+ const responderTWs = (query.pocResponderTermIds || []).map((id) => ({ term: { id }, q: {} }));
50
+ const maxScore = Number(query.maxScore) || 10;
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 terms = [facilityTW, scTW];
58
+ if (pocTW) terms.push(pocTW);
59
+ for (const tw of responderTWs) terms.push(tw);
60
+ const raw = await getData(
61
+ {
62
+ terms,
63
+ filter: query.filter,
64
+ __protected__: query.__protected__
65
+ },
66
+ ds
67
+ );
68
+ if (raw.error) throw raw.error;
69
+ const sampleList = Object.values(raw.samples);
70
+ let sites = [];
71
+ if (!isPublic) {
72
+ for (const s of sampleList) {
73
+ const cell = s[facilityTW.$id];
74
+ if (!cell) continue;
75
+ const val = cell.value;
76
+ let label = facilityTW.term.values?.[val]?.label || val;
77
+ if (typeof label === "string" && label.length > 50) label = label.slice(0, 47) + "...";
78
+ sites.push({ value: val, label });
79
+ }
80
+ if (userSites && query.filterByUserSites) sites = sites.filter((s) => userSites.includes(s.value));
81
+ sites.sort((a, b) => a.label.localeCompare(b.label));
82
+ }
83
+ const eligibleSamples = userSites && query.filterByUserSites ? sampleList.filter((s) => userSites.includes(s[facilityTW.$id]?.value)) : sampleList;
84
+ const scValues = collectScalarValues(eligibleSamples, scTW.$id);
85
+ const responders = responderTWs.map((tw) => {
86
+ const values = collectResponderRatings(eligibleSamples, [tw.$id]);
87
+ return {
88
+ termId: tw.term.id,
89
+ label: responderLabel(tw.term?.name, tw.term.id),
90
+ median: median(values),
91
+ total: values.length,
92
+ distribution: buildDistribution(values, maxScore)
93
+ };
94
+ });
95
+ if (!responders.length && pocTW) {
96
+ const values = collectScalarValues(eligibleSamples, pocTW.$id);
97
+ responders.push({
98
+ termId: pocTW.term.id,
99
+ label: "POC Staff",
100
+ median: median(values),
101
+ total: values.length,
102
+ distribution: buildDistribution(values, maxScore)
103
+ });
104
+ }
105
+ return {
106
+ scMedian: median(scValues),
107
+ scTotal: scValues.length,
108
+ // Shared SC frequency distribution (site counts binned per rating), used for the SC line
109
+ // on the response-distribution chart. Same across every responder group's chart pair.
110
+ scDistribution: buildDistribution(scValues, maxScore),
111
+ responders,
112
+ sites: isPublic ? [] : sites,
113
+ n: eligibleSamples.length
114
+ };
115
+ }
116
+ function responderLabel(name, id) {
117
+ if (!name) return id;
118
+ const stripped = name.replace(/^Impression\s+/i, "").trim();
119
+ return stripped || name;
120
+ }
121
+ function collectScalarValues(samples, $id) {
122
+ const out = [];
123
+ for (const s of samples) {
124
+ const cell = s[$id];
125
+ if (!cell || cell.value == null) continue;
126
+ const n = Number(cell.value);
127
+ if (Number.isFinite(n)) out.push(n);
128
+ }
129
+ return out;
130
+ }
131
+ function collectResponderRatings(samples, $ids) {
132
+ const out = [];
133
+ for (const s of samples) {
134
+ for (const $id of $ids) {
135
+ const cell = s[$id];
136
+ if (!cell || cell.value == null) continue;
137
+ const map = typeof cell.value === "string" ? JSON.parse(cell.value) : cell.value;
138
+ for (const [rating, count] of Object.entries(map)) {
139
+ const r = Number(rating);
140
+ const c = Number(count);
141
+ if (Number.isFinite(r) && Number.isFinite(c)) for (let i = 0; i < c; i++) out.push(r);
142
+ }
143
+ }
144
+ }
145
+ return out;
146
+ }
147
+ function median(values) {
148
+ if (values.length === 0) return null;
149
+ const sorted = [...values].sort((a, b) => a - b);
150
+ const mid = Math.floor(sorted.length / 2);
151
+ const m = sorted.length % 2 !== 0 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
152
+ return Math.round(m);
153
+ }
154
+ function buildDistribution(values, maxScore) {
155
+ const total = values.length;
156
+ const counts = {};
157
+ for (let r = 1; r <= maxScore; r++) counts[r] = 0;
158
+ for (const v of values) {
159
+ const r = Math.round(v);
160
+ if (r >= 1 && r <= maxScore) counts[r] += 1;
161
+ }
162
+ const out = [];
163
+ for (let r = 1; r <= maxScore; r++) {
164
+ const count = counts[r];
165
+ const pct = total > 0 ? count / total * 100 : 0;
166
+ out.push({ rating: r, count, pct: Math.round(pct * 10) / 10 });
167
+ }
168
+ return out;
169
+ }
170
+ export {
171
+ api,
172
+ buildDistribution,
173
+ collectResponderRatings,
174
+ collectScalarValues,
175
+ median
176
+ };
@@ -264,7 +264,7 @@ async function validate_query_proteome(ds) {
264
264
  throw `Missing controlFilter in queries.proteome.organisms.${organismName}.assays.${assayName}.cohorts.${cohortName}`;
265
265
  if (!cohort.caseFilter)
266
266
  throw `Missing caseFilter in queries.proteome.organisms.${organismName}.assays.${assayName}.cohorts.${cohortName}`;
267
- if (!cohort.prior?.d0 || !cohort.prior?.s0sq)
267
+ if (!cohort.DAPfile && (!cohort.prior?.d0 || !cohort.prior?.s0sq))
268
268
  throw `Missing prior.d0 and prior.s0sq in queries.proteome.organisms.${organismName}.assays.${assayName}.cohorts.${cohortName}`;
269
269
  }
270
270
  } else {