@sjcrh/proteinpaint-server 2.198.0 → 2.200.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.
@@ -359,7 +359,6 @@ function termdb_test_default() {
359
359
  file: "files/hg38/TermdbTest/TermdbTest_ITD.gz"
360
360
  },
361
361
  singleSampleMutation: {
362
- src: "native",
363
362
  sample_id_key: "sample_id",
364
363
  folder: "files/hg38/TermdbTest/mutationpersample/"
365
364
  },
@@ -389,7 +388,6 @@ function termdb_test_default() {
389
388
  }
390
389
  },
391
390
  geneExpression: {
392
- src: "native",
393
391
  file: "files/hg38/TermdbTest/rnaseq/TermdbTest.fpkm.matrix.new.h5",
394
392
  unit: "FPKM"
395
393
  },
@@ -419,7 +417,6 @@ function termdb_test_default() {
419
417
  },
420
418
  data: {
421
419
  sameLegend: true,
422
- src: "native",
423
420
  plots: [
424
421
  {
425
422
  name: "UMAP",
@@ -437,7 +434,6 @@ function termdb_test_default() {
437
434
  ]
438
435
  },
439
436
  geneExpression: {
440
- src: "native",
441
437
  folder: "files/hg38/TermdbTest/scrna/geneExpHdf5"
442
438
  }
443
439
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjcrh/proteinpaint-server",
3
- "version": "2.198.0",
3
+ "version": "2.200.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",
61
+ "@sjcrh/proteinpaint-python": "2.199.0",
62
+ "@sjcrh/proteinpaint-r": "2.199.0",
64
63
  "@sjcrh/proteinpaint-rust": "2.198.0",
65
- "@sjcrh/proteinpaint-shared": "2.198.0",
66
- "@sjcrh/proteinpaint-types": "2.198.0",
64
+ "@sjcrh/proteinpaint-shared": "2.200.0",
65
+ "@sjcrh/proteinpaint-types": "2.200.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 {