@sjcrh/proteinpaint-server 2.167.2 → 2.169.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.
- package/package.json +5 -5
- package/routes/saveWSIAnnotation.js +6 -1
- package/routes/termdb.boxplot.js +32 -11
- package/routes/termdb.chat.js +43 -14
- package/src/app.js +98 -45
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjcrh/proteinpaint-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.169.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",
|
|
@@ -62,11 +62,11 @@
|
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"@sjcrh/augen": "2.143.0",
|
|
65
|
-
"@sjcrh/proteinpaint-python": "2.
|
|
65
|
+
"@sjcrh/proteinpaint-python": "2.169.0",
|
|
66
66
|
"@sjcrh/proteinpaint-r": "2.152.1-0",
|
|
67
|
-
"@sjcrh/proteinpaint-rust": "2.
|
|
68
|
-
"@sjcrh/proteinpaint-shared": "2.
|
|
69
|
-
"@sjcrh/proteinpaint-types": "2.
|
|
67
|
+
"@sjcrh/proteinpaint-rust": "2.169.0",
|
|
68
|
+
"@sjcrh/proteinpaint-shared": "2.169.0",
|
|
69
|
+
"@sjcrh/proteinpaint-types": "2.169.0",
|
|
70
70
|
"@types/express": "^5.0.0",
|
|
71
71
|
"@types/express-session": "^1.18.1",
|
|
72
72
|
"better-sqlite3": "^12.4.1",
|
|
@@ -81,7 +81,12 @@ function validateQuery(ds, connection) {
|
|
|
81
81
|
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
82
82
|
`;
|
|
83
83
|
const insertStmt = connection.prepare(insertSql);
|
|
84
|
-
const userRow = connection.prepare(
|
|
84
|
+
const userRow = connection.prepare(
|
|
85
|
+
`SELECT id
|
|
86
|
+
FROM project_users
|
|
87
|
+
ORDER BY id
|
|
88
|
+
LIMIT 1`
|
|
89
|
+
).get();
|
|
85
90
|
const userId = userRow?.id;
|
|
86
91
|
insertStmt.run(projectId, userId, coords, timestamp, status, classId, imageId);
|
|
87
92
|
return { status: "ok" };
|
package/routes/termdb.boxplot.js
CHANGED
|
@@ -2,8 +2,9 @@ import { boxplotPayload } from "#types/checkers";
|
|
|
2
2
|
import { getData } from "../src/termdb.matrix.js";
|
|
3
3
|
import { boxplot_getvalue } from "../src/utils.js";
|
|
4
4
|
import { sortPlot2Values } from "./termdb.violin.ts";
|
|
5
|
-
import { getDescrStats } from "./termdb.descrstats.ts";
|
|
5
|
+
import { getDescrStats, getStdDev, getMean } from "./termdb.descrstats.ts";
|
|
6
6
|
import { run_rust } from "@sjcrh/proteinpaint-rust";
|
|
7
|
+
import { roundValueAuto } from "#shared/roundValue.js";
|
|
7
8
|
const api = {
|
|
8
9
|
endpoint: "termdb/boxplot",
|
|
9
10
|
methods: {
|
|
@@ -20,14 +21,14 @@ const api = {
|
|
|
20
21
|
function init({ genomes }) {
|
|
21
22
|
return async (req, res) => {
|
|
22
23
|
const q = req.query;
|
|
24
|
+
const genome = genomes[q.genome];
|
|
25
|
+
if (!genome) throw new Error("invalid genome name");
|
|
26
|
+
const ds = genome.datasets?.[q.dslabel];
|
|
27
|
+
if (!ds) throw new Error("invalid ds");
|
|
28
|
+
const terms = [q.tw];
|
|
29
|
+
if (q.overlayTw) terms.push(q.overlayTw);
|
|
30
|
+
if (q.divideTw) terms.push(q.divideTw);
|
|
23
31
|
try {
|
|
24
|
-
const genome = genomes[q.genome];
|
|
25
|
-
if (!genome) throw new Error("invalid genome name");
|
|
26
|
-
const ds = genome.datasets?.[q.dslabel];
|
|
27
|
-
if (!ds) throw new Error("invalid ds");
|
|
28
|
-
const terms = [q.tw];
|
|
29
|
-
if (q.overlayTw) terms.push(q.overlayTw);
|
|
30
|
-
if (q.divideTw) terms.push(q.divideTw);
|
|
31
32
|
const data = await getData({ filter: q.filter, filter0: q.filter0, terms, __protected__: q.__protected__ }, ds);
|
|
32
33
|
if (data.error) throw new Error(data.error);
|
|
33
34
|
const { absMin, absMax, charts, uncomputableValues, descrStats, outlierMin, outlierMax } = await processData(
|
|
@@ -89,9 +90,13 @@ async function processData(data, q) {
|
|
|
89
90
|
if (q.orderByMedian == true) {
|
|
90
91
|
plots.sort((a, b) => a.boxplot.p50 - b.boxplot.p50);
|
|
91
92
|
}
|
|
92
|
-
|
|
93
|
+
const sampleCount = plots.reduce((total, p) => {
|
|
94
|
+
if (p.hidden) return total;
|
|
95
|
+
return total + p.descrStats.total.value;
|
|
96
|
+
}, 0);
|
|
97
|
+
charts[chart] = { chartId: chart, plots, sampleCount };
|
|
93
98
|
}
|
|
94
|
-
if (q.showAssocTests
|
|
99
|
+
if (q.showAssocTests && overlayTerm) await getWilcoxonData(charts);
|
|
95
100
|
Object.keys(charts).forEach((c) => charts[c].plots.forEach((p) => delete p.tempValues));
|
|
96
101
|
return { absMin, absMax, charts, uncomputableValues, descrStats, outlierMin, outlierMax };
|
|
97
102
|
}
|
|
@@ -109,7 +114,7 @@ function setPlotData(plots, values, key, sampleType, descrStats, q, data, outlie
|
|
|
109
114
|
if (!boxplot) throw new Error("boxplot_getvalue failed [termdb.boxplot init()]");
|
|
110
115
|
const _plot = {
|
|
111
116
|
boxplot,
|
|
112
|
-
descrStats,
|
|
117
|
+
descrStats: setIndividualBoxPlotStats(boxplot, sortedValues),
|
|
113
118
|
//quick fix
|
|
114
119
|
//to delete later
|
|
115
120
|
tempValues: sortedValues
|
|
@@ -136,6 +141,22 @@ function setPlotData(plots, values, key, sampleType, descrStats, q, data, outlie
|
|
|
136
141
|
}
|
|
137
142
|
return [outlierMax, outlierMin];
|
|
138
143
|
}
|
|
144
|
+
function setIndividualBoxPlotStats(boxplot, values) {
|
|
145
|
+
const stats = {
|
|
146
|
+
total: { key: "total", label: "Total", value: values.length },
|
|
147
|
+
min: { key: "min", label: "Minimum", value: values[0] },
|
|
148
|
+
p25: { key: "p25", label: "1st quartile", value: boxplot.p25 },
|
|
149
|
+
median: { key: "median", label: "Median", value: boxplot.p50 },
|
|
150
|
+
p75: { key: "p75", label: "3rd quartile", value: boxplot.p75 },
|
|
151
|
+
mean: { key: "mean", label: "Mean", value: getMean(values) },
|
|
152
|
+
max: { key: "max", label: "Maximum", value: values[values.length - 1] },
|
|
153
|
+
stdDev: { key: "stdDev", label: "Standard deviation", value: getStdDev(values) }
|
|
154
|
+
};
|
|
155
|
+
for (const key of Object.keys(stats)) {
|
|
156
|
+
stats[key].value = roundValueAuto(stats[key].value);
|
|
157
|
+
}
|
|
158
|
+
return stats;
|
|
159
|
+
}
|
|
139
160
|
function setHiddenPlots(term, plots) {
|
|
140
161
|
for (const v of Object.values(term.term?.values)) {
|
|
141
162
|
const plot = plots.find((p) => p.key === v.label);
|
package/routes/termdb.chat.js
CHANGED
|
@@ -59,15 +59,19 @@ function init({ genomes }) {
|
|
|
59
59
|
throw "llm_backend either needs to be 'SJ' or 'ollama'";
|
|
60
60
|
}
|
|
61
61
|
const chatbot_input = {
|
|
62
|
-
// Just hardcoding variables here, these will later be defined in more appropriate places
|
|
63
62
|
user_input: q.prompt,
|
|
64
63
|
apilink,
|
|
65
64
|
tpmasterdir: serverconfig.tpmasterdir,
|
|
66
65
|
comp_model_name,
|
|
67
66
|
embedding_model_name,
|
|
67
|
+
dataset_db: ds.cohort.db.file,
|
|
68
|
+
genedb: g.genedb.dbfile,
|
|
69
|
+
aiRoute: serverconfig.aiRoute,
|
|
70
|
+
// Route file for classifying chat request into various routes
|
|
68
71
|
llm_backend_name: serverconfig.llm_backend,
|
|
69
72
|
// The type of backend (engine) used for running the embedding and completion model. Currently "SJ" and "Ollama" are supported
|
|
70
73
|
aifiles: serverconfig_ds_entries.aifiles,
|
|
74
|
+
// Dataset specific data containing data-specific routes, system prompts for agents and few-shot examples
|
|
71
75
|
binpath: serverconfig.binpath
|
|
72
76
|
};
|
|
73
77
|
const time1 = (/* @__PURE__ */ new Date()).valueOf();
|
|
@@ -87,23 +91,48 @@ function init({ genomes }) {
|
|
|
87
91
|
if (ai_output_json.plot.simpleFilter) {
|
|
88
92
|
if (!Array.isArray(ai_output_json.plot.simpleFilter)) throw "ai_output_json.plot.simpleFilter is not array";
|
|
89
93
|
const localfilter = { type: "tvslst", in: true, join: "", lst: [] };
|
|
94
|
+
if (ai_output_json.plot.simpleFilter.length > 1) localfilter.join = "and";
|
|
90
95
|
for (const f of ai_output_json.plot.simpleFilter) {
|
|
91
96
|
const term = ds.cohort.termdb.q.termjsonByOneid(f.term);
|
|
92
97
|
if (!term) throw "invalid term id from simpleFilter[].term";
|
|
93
|
-
if (term.type
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
98
|
+
if (term.type == "categorical") {
|
|
99
|
+
let cat;
|
|
100
|
+
for (const ck in term.values) {
|
|
101
|
+
if (ck == f.category) cat = ck;
|
|
102
|
+
else if (term.values[ck].label == f.category) cat = ck;
|
|
103
|
+
}
|
|
104
|
+
if (!cat) throw "invalid category from " + JSON.stringify(f);
|
|
105
|
+
localfilter.lst.push({
|
|
106
|
+
type: "tvs",
|
|
107
|
+
tvs: {
|
|
108
|
+
term,
|
|
109
|
+
values: [{ key: cat }]
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
} else if (term.type == "float" || term.type == "integer") {
|
|
113
|
+
const numeric = {
|
|
114
|
+
type: "tvs",
|
|
115
|
+
tvs: {
|
|
116
|
+
term,
|
|
117
|
+
ranges: []
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
const range = {};
|
|
121
|
+
if (f.gt && !f.lt) {
|
|
122
|
+
range.start = Number(f.gt);
|
|
123
|
+
range.stopunbounded = true;
|
|
124
|
+
} else if (f.lt && !f.gt) {
|
|
125
|
+
range.stop = Number(f.lt);
|
|
126
|
+
range.startunbounded = true;
|
|
127
|
+
} else if (f.gt && f.lt) {
|
|
128
|
+
range.start = Number(f.gt);
|
|
129
|
+
range.stop = Number(f.lt);
|
|
130
|
+
} else {
|
|
131
|
+
throw "Neither greater or lesser defined";
|
|
105
132
|
}
|
|
106
|
-
|
|
133
|
+
numeric.tvs.ranges.push(range);
|
|
134
|
+
localfilter.lst.push(numeric);
|
|
135
|
+
}
|
|
107
136
|
}
|
|
108
137
|
delete ai_output_json.plot.simpleFilter;
|
|
109
138
|
ai_output_json.plot.filter = localfilter;
|