@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.
- package/package.json +9 -12
- package/routes/aiProjectAdmin.js +2 -28
- package/routes/aiProjectSelectedWSImages.js +2 -17
- package/routes/brainImaging.js +1 -15
- package/routes/brainImagingSamples.js +1 -15
- package/routes/burden.js +1 -15
- package/routes/correlationVolcano.js +1 -15
- package/routes/dataset.js +1 -16
- package/routes/deleteWSITileSelection.js +2 -12
- package/routes/dsdata.js +1 -19
- package/routes/gdc.grin2.list.js +1 -15
- package/routes/gdc.grin2.run.js +1 -15
- package/routes/gdc.maf.js +1 -15
- package/routes/gdc.mafBuild.js +1 -15
- package/routes/genesetEnrichment.js +129 -97
- package/routes/grin2.js +110 -79
- package/routes/saveWSIAnnotation.js +2 -13
- package/routes/termdb.DE.js +137 -54
- package/routes/termdb.categories.js +2 -16
- package/routes/termdb.chat.js +169 -1076
- package/routes/termdb.cluster.js +5 -16
- package/routes/termdb.config.js +12 -17
- package/routes/termdb.descrstats.js +2 -16
- package/routes/termdb.diffMeth.js +100 -21
- package/routes/termdb.geneRanking.js +139 -0
- package/routes/termdb.proteome.js +1 -15
- package/routes/termdb.runChart.js +16 -30
- package/routes/termdb.sampleScatter.js +7 -97
- package/routes/termdb.singleCellPlots.js +159 -0
- package/routes/termdb.singlecellSamples.js +6 -16
- package/routes/termdb.violinBox.js +1 -15
- package/routes/wsimages.js +1 -16
- package/src/app.js +4014 -4127
- package/routes/_template_.js +0 -33
- package/routes/aiProjectTrainModel.js +0 -68
- package/routes/alphaGenome.js +0 -41
- package/routes/alphaGenomeTypes.js +0 -36
- package/routes/dzimages.js +0 -55
- package/routes/gene2canonicalisoform.js +0 -37
- package/routes/genelookup.js +0 -32
- package/routes/genesetOverrepresentation.js +0 -49
- package/routes/genomes.js +0 -150
- package/routes/healthcheck.js +0 -35
- package/routes/hicdata.js +0 -74
- package/routes/hicgenome.js +0 -75
- package/routes/hicstat.js +0 -35
- package/routes/img.js +0 -46
- package/routes/isoformlst.js +0 -48
- package/routes/ntseq.js +0 -36
- package/routes/pdomain.js +0 -53
- package/routes/profile.barchart2.js +0 -114
- package/routes/profile.forms2.js +0 -107
- package/routes/profile.polar2.js +0 -101
- package/routes/profile.radar2.js +0 -112
- package/routes/profile.radarFacility2.js +0 -148
- package/routes/sampledzimages.js +0 -48
- package/routes/samplewsimages.js +0 -60
- package/routes/snp.js +0 -98
- package/routes/termdb.chat2.js +0 -217
- package/routes/termdb.chat3.js +0 -209
- package/routes/termdb.cohort.summary.js +0 -37
- package/routes/termdb.cohorts.js +0 -41
- package/routes/termdb.dapVolcano.js +0 -80
- package/routes/termdb.dmr.js +0 -93
- package/routes/termdb.filterTermValues.js +0 -89
- package/routes/termdb.isoformAvailability.js +0 -35
- package/routes/termdb.numericcategories.js +0 -46
- package/routes/termdb.percentile.js +0 -66
- package/routes/termdb.profileFormScores.js +0 -92
- package/routes/termdb.profileScores.js +0 -113
- package/routes/termdb.rootterm.js +0 -39
- package/routes/termdb.sampleImages.js +0 -63
- package/routes/termdb.singleSampleMutation.js +0 -75
- package/routes/termdb.singlecellDEgenes.js +0 -55
- package/routes/termdb.singlecellData.js +0 -39
- package/routes/termdb.termchildren.js +0 -42
- package/routes/termdb.termsbyids.js +0 -50
- package/routes/termdb.topMutatedGenes.js +0 -127
- package/routes/termdb.topTermsByType.js +0 -96
- package/routes/termdb.topVariablyExpressedGenes.js +0 -132
- package/routes/tileserver.js +0 -68
- package/routes/wsisamples.js +0 -71
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import { ProfileScoresPayload } from "#types/checkers";
|
|
2
|
-
import { getData } from "../src/termdb.matrix.js";
|
|
3
|
-
const api = {
|
|
4
|
-
endpoint: "termdb/profileRadarFacility2Scores",
|
|
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 isAdmin = cohortAuth?.role === "admin";
|
|
57
|
-
const userSites = cohortAuth?.sites;
|
|
58
|
-
const raw = await getData(
|
|
59
|
-
{
|
|
60
|
-
terms,
|
|
61
|
-
filter: query.filter,
|
|
62
|
-
__protected__: query.__protected__
|
|
63
|
-
},
|
|
64
|
-
ds
|
|
65
|
-
);
|
|
66
|
-
if (raw.error) throw raw.error;
|
|
67
|
-
const samples = Object.values(raw.samples);
|
|
68
|
-
let sites = samples.map((s) => {
|
|
69
|
-
const val = s[facilityTW.$id].value;
|
|
70
|
-
let label = facilityTW.term.values?.[val]?.label || val;
|
|
71
|
-
if (label.length > 50) label = label.slice(0, 47) + "...";
|
|
72
|
-
return { value: val, label };
|
|
73
|
-
});
|
|
74
|
-
if (userSites && !isAdmin) {
|
|
75
|
-
sites = sites.filter((s) => userSites.includes(s.value));
|
|
76
|
-
}
|
|
77
|
-
sites.sort((a, b) => a.label.localeCompare(b.label));
|
|
78
|
-
const eligibleSamples = userSites && query.filterByUserSites ? samples.filter((s) => userSites.includes(s[facilityTW.$id].value)) : samples;
|
|
79
|
-
const aggregateScore = {};
|
|
80
|
-
for (const d of query.scoreTerms) {
|
|
81
|
-
const score = computeMedianPercentage(d, eligibleSamples);
|
|
82
|
-
if (score !== null) aggregateScore[d.score.term.id] = score;
|
|
83
|
-
}
|
|
84
|
-
let targetSiteValue = null;
|
|
85
|
-
if (query.facilitySite) {
|
|
86
|
-
if (isAdmin) targetSiteValue = query.facilitySite;
|
|
87
|
-
else if (userSites?.includes(query.facilitySite)) targetSiteValue = query.facilitySite;
|
|
88
|
-
}
|
|
89
|
-
if (!targetSiteValue && sites.length > 0) targetSiteValue = sites[0].value;
|
|
90
|
-
let sampleData = void 0;
|
|
91
|
-
if (!isPublic && targetSiteValue) {
|
|
92
|
-
const sampleRow = samples.find((s) => s[facilityTW.$id].value == targetSiteValue);
|
|
93
|
-
if (sampleRow) {
|
|
94
|
-
const site = sites.find((s) => s.value == targetSiteValue) || {
|
|
95
|
-
value: targetSiteValue,
|
|
96
|
-
label: facilityTW.term.values?.[targetSiteValue]?.label || targetSiteValue
|
|
97
|
-
};
|
|
98
|
-
const singleSiteScore = {};
|
|
99
|
-
for (const d of query.scoreTerms) {
|
|
100
|
-
const percent = computeSinglePercentage(d, sampleRow);
|
|
101
|
-
if (percent !== null) singleSiteScore[d.score.term.id] = percent;
|
|
102
|
-
}
|
|
103
|
-
sampleData = { term2Score: singleSiteScore, site, sites, n: 1 };
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return {
|
|
107
|
-
term2Score: aggregateScore,
|
|
108
|
-
sampleData,
|
|
109
|
-
// Public users: empty (chart should be unreachable anyway via isSupportedChartOverride)
|
|
110
|
-
sites: isPublic ? [] : sites,
|
|
111
|
-
n: eligibleSamples.length
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
function computeSinglePercentage(d, sample) {
|
|
115
|
-
const scoreValue = sample[d.score.$id]?.value;
|
|
116
|
-
if (scoreValue == null) return null;
|
|
117
|
-
let maxScoreValue = null;
|
|
118
|
-
if (typeof d.maxScore === "number") {
|
|
119
|
-
maxScoreValue = d.maxScore;
|
|
120
|
-
} else {
|
|
121
|
-
maxScoreValue = sample[d.maxScore.$id]?.value;
|
|
122
|
-
}
|
|
123
|
-
if (maxScoreValue == null || maxScoreValue === 0) return null;
|
|
124
|
-
return Math.round(scoreValue / maxScoreValue * 100);
|
|
125
|
-
}
|
|
126
|
-
function computeMedianPercentage(d, samples) {
|
|
127
|
-
const percentages = [];
|
|
128
|
-
for (const s of samples) {
|
|
129
|
-
const scoreValue = s[d.score.$id]?.value;
|
|
130
|
-
if (scoreValue == null) continue;
|
|
131
|
-
let maxScoreValue = null;
|
|
132
|
-
if (typeof d.maxScore === "number") {
|
|
133
|
-
maxScoreValue = d.maxScore;
|
|
134
|
-
} else {
|
|
135
|
-
maxScoreValue = s[d.maxScore.$id]?.value;
|
|
136
|
-
}
|
|
137
|
-
if (maxScoreValue == null || maxScoreValue === 0) continue;
|
|
138
|
-
percentages.push(scoreValue / maxScoreValue * 100);
|
|
139
|
-
}
|
|
140
|
-
if (percentages.length === 0) return null;
|
|
141
|
-
percentages.sort((a, b) => a - b);
|
|
142
|
-
const mid = Math.floor(percentages.length / 2);
|
|
143
|
-
const median = percentages.length % 2 !== 0 ? percentages[mid] : (percentages[mid - 1] + percentages[mid]) / 2;
|
|
144
|
-
return Math.round(median);
|
|
145
|
-
}
|
|
146
|
-
export {
|
|
147
|
-
api
|
|
148
|
-
};
|
package/routes/sampledzimages.js
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import serverconfig from "#src/serverconfig.js";
|
|
4
|
-
import { dzImagesPayload } from "#types/checkers";
|
|
5
|
-
const api = {
|
|
6
|
-
endpoint: "sampledzimages",
|
|
7
|
-
methods: {
|
|
8
|
-
get: {
|
|
9
|
-
init,
|
|
10
|
-
...dzImagesPayload
|
|
11
|
-
},
|
|
12
|
-
post: {
|
|
13
|
-
init,
|
|
14
|
-
...dzImagesPayload
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
function init({ genomes }) {
|
|
19
|
-
return async (req, res) => {
|
|
20
|
-
const q = req.query;
|
|
21
|
-
try {
|
|
22
|
-
const g = genomes[q.genome];
|
|
23
|
-
if (!g) throw "invalid genome name";
|
|
24
|
-
const ds = g.datasets[q.dslabel];
|
|
25
|
-
if (!ds) throw "invalid dataset name";
|
|
26
|
-
const sampleId = q.sample_id || "";
|
|
27
|
-
const sampleDZImagesPath = path.join(
|
|
28
|
-
`${serverconfig.tpmasterdir}/${ds.queries.DZImages.imageBySampleFolder}`,
|
|
29
|
-
sampleId
|
|
30
|
-
);
|
|
31
|
-
const sampleDZImages = getDZImages(sampleDZImagesPath);
|
|
32
|
-
res.send(
|
|
33
|
-
{ sampleDZImages }
|
|
34
|
-
/*satisfies DZImagesResponse*/
|
|
35
|
-
);
|
|
36
|
-
} catch (e) {
|
|
37
|
-
console.log(e);
|
|
38
|
-
res.status(404).send("Sample images not found");
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
function getDZImages(sampleImagesPath) {
|
|
43
|
-
const files = fs.readdirSync(sampleImagesPath);
|
|
44
|
-
return files.filter((file) => path.extname(file) === ".dzi");
|
|
45
|
-
}
|
|
46
|
-
export {
|
|
47
|
-
api
|
|
48
|
-
};
|
package/routes/samplewsimages.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { sampleWSImagesPayload } from "#types/checkers";
|
|
2
|
-
const api = {
|
|
3
|
-
endpoint: "samplewsimages",
|
|
4
|
-
methods: {
|
|
5
|
-
get: {
|
|
6
|
-
...sampleWSImagesPayload,
|
|
7
|
-
init
|
|
8
|
-
},
|
|
9
|
-
post: {
|
|
10
|
-
...sampleWSImagesPayload,
|
|
11
|
-
init
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
};
|
|
15
|
-
function init({ genomes }) {
|
|
16
|
-
return async (req, res) => {
|
|
17
|
-
try {
|
|
18
|
-
const query = req.query;
|
|
19
|
-
const g = genomes[query.genome];
|
|
20
|
-
if (!g) throw "invalid genome name";
|
|
21
|
-
const ds = g.datasets[query.dslabel];
|
|
22
|
-
if (!ds) throw "invalid dataset name";
|
|
23
|
-
const sampleId = query.sample_id;
|
|
24
|
-
const wsimages = await ds.queries.WSImages.getWSImages(sampleId);
|
|
25
|
-
res.send({ sampleWSImages: wsimages });
|
|
26
|
-
} catch (e) {
|
|
27
|
-
console.log(e);
|
|
28
|
-
res.status(404).send("Sample images not found");
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
async function validate_query_getSampleWSImages(ds) {
|
|
33
|
-
if (!ds.queries?.WSImages) return;
|
|
34
|
-
validateQuery(ds);
|
|
35
|
-
}
|
|
36
|
-
function validateQuery(ds) {
|
|
37
|
-
if (typeof ds.queries.WSImages.getWSImages == "function") {
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
ds.queries.WSImages.getWSImages = async (sampleName) => {
|
|
41
|
-
const sql = `SELECT wsimages.filename as filename, wsimages.metadata as metadata
|
|
42
|
-
FROM wsimages
|
|
43
|
-
INNER JOIN sampleidmap
|
|
44
|
-
ON wsimages.sample = sampleidmap.id
|
|
45
|
-
WHERE sampleidmap.name = ?`;
|
|
46
|
-
const rows = ds.cohort.db.connection.prepare(sql).all(sampleName);
|
|
47
|
-
const images = [];
|
|
48
|
-
for (const row of rows) {
|
|
49
|
-
images.push({
|
|
50
|
-
filename: row.filename,
|
|
51
|
-
metadata: row.metadata
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
return images;
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
export {
|
|
58
|
-
api,
|
|
59
|
-
validate_query_getSampleWSImages
|
|
60
|
-
};
|
package/routes/snp.js
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { snpPayload } from "#types/checkers";
|
|
2
|
-
import * as utils from "#src/utils.js";
|
|
3
|
-
const api = {
|
|
4
|
-
// route endpoint
|
|
5
|
-
// - no need for trailing slash
|
|
6
|
-
// - should be a noun (method is based on HTTP GET, POST, etc)
|
|
7
|
-
// - don't add 'Data' as response is assumed to be data
|
|
8
|
-
endpoint: "snp",
|
|
9
|
-
methods: {
|
|
10
|
-
get: {
|
|
11
|
-
...snpPayload,
|
|
12
|
-
init
|
|
13
|
-
},
|
|
14
|
-
post: {
|
|
15
|
-
...snpPayload,
|
|
16
|
-
init
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
function init({ genomes }) {
|
|
21
|
-
return async function handle_snp(req, res) {
|
|
22
|
-
try {
|
|
23
|
-
const q = req.query;
|
|
24
|
-
const n = q.genome;
|
|
25
|
-
if (!n) throw "no genome";
|
|
26
|
-
const results = await searchSNP(q, genomes[n]);
|
|
27
|
-
res.send({ results });
|
|
28
|
-
} catch (e) {
|
|
29
|
-
if (e.stack) console.log(e.stack);
|
|
30
|
-
return res.send({ error: e.message || e });
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
async function searchSNP(q, genome) {
|
|
35
|
-
if (!genome) throw "invalid genome";
|
|
36
|
-
if (!genome.snp) throw "snp is not configured for this genome";
|
|
37
|
-
const hits = [];
|
|
38
|
-
if (q.byCoord) {
|
|
39
|
-
if (genome.genomicNameRegexp.test(q.chr)) throw "invalid chr name";
|
|
40
|
-
if (!Array.isArray(q.ranges)) throw "ranges not an array";
|
|
41
|
-
for (const r of q.ranges) {
|
|
42
|
-
if (!Number.isInteger(r.start) || !Number.isInteger(r.stop) || r.start < 0 || r.stop < r.start)
|
|
43
|
-
throw "invalid start/stop";
|
|
44
|
-
if (r.stop - r.start >= 100) {
|
|
45
|
-
throw "range too big";
|
|
46
|
-
}
|
|
47
|
-
const snps = await utils.query_bigbed_by_coord(genome.snp.bigbedfile, q.chr, r.start, r.stop);
|
|
48
|
-
for (const snp of snps) {
|
|
49
|
-
const hit = snp2hit(snp);
|
|
50
|
-
if (q.alleleLst) {
|
|
51
|
-
let missing = false;
|
|
52
|
-
for (const i of q.alleleLst) {
|
|
53
|
-
if (i && !hit.alleles.includes(i)) {
|
|
54
|
-
missing = true;
|
|
55
|
-
break;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
if (missing) continue;
|
|
59
|
-
}
|
|
60
|
-
hits.push(hit);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
} else if (q.byName) {
|
|
64
|
-
if (!Array.isArray(q.lst)) throw ".lst[] missing";
|
|
65
|
-
for (const n of q.lst) {
|
|
66
|
-
if (genome.genomicNameRegexp.test(n)) continue;
|
|
67
|
-
const snps = await utils.query_bigbed_by_name(genome.snp.bigbedfile, n);
|
|
68
|
-
for (const snp of snps) {
|
|
69
|
-
const hit = snp2hit(snp);
|
|
70
|
-
hits.push(hit);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
} else {
|
|
74
|
-
throw "unknown query method";
|
|
75
|
-
}
|
|
76
|
-
return hits;
|
|
77
|
-
}
|
|
78
|
-
function snp2hit(snp) {
|
|
79
|
-
const fields = snp.split(" ");
|
|
80
|
-
const ref = fields[4];
|
|
81
|
-
const alts = fields[6].split(",").filter(Boolean);
|
|
82
|
-
const observed = ref + "/" + alts.join("/");
|
|
83
|
-
const hit = {
|
|
84
|
-
chrom: fields[0],
|
|
85
|
-
chromStart: Number(fields[1]),
|
|
86
|
-
chromEnd: Number(fields[2]),
|
|
87
|
-
name: fields[3],
|
|
88
|
-
observed,
|
|
89
|
-
ref,
|
|
90
|
-
alt: alts,
|
|
91
|
-
alleles: [ref, ...alts]
|
|
92
|
-
};
|
|
93
|
-
return hit;
|
|
94
|
-
}
|
|
95
|
-
export {
|
|
96
|
-
api,
|
|
97
|
-
searchSNP
|
|
98
|
-
};
|
package/routes/termdb.chat2.js
DELETED
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
import { ChatPayload } from "#types/checkers";
|
|
2
|
-
import { classifyQuery } from "./chat/classify1.ts";
|
|
3
|
-
import { classifyNotPlot } from "./chat/classify2.ts";
|
|
4
|
-
import { classifyPlotType } from "./chat/plot.ts";
|
|
5
|
-
import { extract_DE_search_terms_from_query } from "./chat/DEagent.ts";
|
|
6
|
-
import { determineAmbiguousGenePrompt } from "./chat/ambiguousgeneagent.ts";
|
|
7
|
-
import { extract_summary_terms } from "./chat/summaryagent.ts";
|
|
8
|
-
import { extract_matrix_search_terms_from_query } from "./chat/matrixagent.ts";
|
|
9
|
-
import { extract_samplescatter_terms_from_query } from "./chat/samplescatteragent.ts";
|
|
10
|
-
import { extract_hiercluster_terms_from_query } from "./chat/hierclusteragent.ts";
|
|
11
|
-
import { classifyGeneDataType } from "./chat/genedatatypeagent.ts";
|
|
12
|
-
import { extractGenesFromPrompt, parse_dataset_db, parse_geneset_db, getGenesetNames } from "./chat/utils.ts";
|
|
13
|
-
import serverconfig from "../src/serverconfig.js";
|
|
14
|
-
import { mayLog } from "#src/helpers.ts";
|
|
15
|
-
import { formatElapsedTime } from "#shared";
|
|
16
|
-
import path from "path";
|
|
17
|
-
import fs from "fs";
|
|
18
|
-
import { readJSONFile } from "./chat/utils.ts";
|
|
19
|
-
const api = {
|
|
20
|
-
endpoint: "termdb/chat2",
|
|
21
|
-
methods: {
|
|
22
|
-
get: {
|
|
23
|
-
...ChatPayload,
|
|
24
|
-
init
|
|
25
|
-
},
|
|
26
|
-
post: {
|
|
27
|
-
...ChatPayload,
|
|
28
|
-
init
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
function init({ genomes }) {
|
|
33
|
-
return async (req, res) => {
|
|
34
|
-
const q = req.query;
|
|
35
|
-
try {
|
|
36
|
-
const g = genomes[q.genome];
|
|
37
|
-
if (!g) throw "invalid genome";
|
|
38
|
-
const ds = g.datasets?.[q.dslabel];
|
|
39
|
-
if (!ds) throw "invalid dslabel";
|
|
40
|
-
const aiFilesDir = serverconfig.binpath + "/../../dataset/ai/" + q.dslabel;
|
|
41
|
-
let agentFiles = [];
|
|
42
|
-
try {
|
|
43
|
-
agentFiles = fs.readdirSync(aiFilesDir).filter((file) => file.endsWith(".json"));
|
|
44
|
-
} catch (err) {
|
|
45
|
-
if (err.code === "ENOENT") throw new Error(`Directory not found: ${aiFilesDir}`);
|
|
46
|
-
if (err.code === "ENOTDIR") throw new Error(`Path is not a directory: ${aiFilesDir}`);
|
|
47
|
-
throw err;
|
|
48
|
-
}
|
|
49
|
-
const llm = serverconfig.llm;
|
|
50
|
-
if (!llm) throw "serverconfig.llm is not configured";
|
|
51
|
-
if (llm.provider !== "SJ" && llm.provider !== "ollama") {
|
|
52
|
-
throw "llm.provider must be 'SJ' or 'ollama'";
|
|
53
|
-
}
|
|
54
|
-
const dataset_db = serverconfig.tpmasterdir + "/" + ds.cohort.db.file;
|
|
55
|
-
const genedb = serverconfig.tpmasterdir + "/" + g.genedb.dbfile;
|
|
56
|
-
const testing = false;
|
|
57
|
-
const genesetNames = getGenesetNames(g);
|
|
58
|
-
const ai_output_json = await run_chat_pipeline(
|
|
59
|
-
q.prompt,
|
|
60
|
-
llm,
|
|
61
|
-
testing,
|
|
62
|
-
dataset_db,
|
|
63
|
-
genedb,
|
|
64
|
-
ds,
|
|
65
|
-
genesetNames,
|
|
66
|
-
agentFiles,
|
|
67
|
-
aiFilesDir
|
|
68
|
-
);
|
|
69
|
-
res.send(ai_output_json);
|
|
70
|
-
} catch (e) {
|
|
71
|
-
if (e.stack) mayLog(e.stack);
|
|
72
|
-
res.send({ error: e?.message || e });
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
async function run_chat_pipeline(user_prompt, llm, testing, dataset_db, genedb, ds, genesetNames = [], agentFiles, aiFilesDir) {
|
|
77
|
-
if (!fs.existsSync(path.join(aiFilesDir, "main.json")))
|
|
78
|
-
throw "Main data file is not specified for dataset:" + ds.label;
|
|
79
|
-
const dataset_json = await readJSONFile(path.join(aiFilesDir, "main.json"));
|
|
80
|
-
const time1 = (/* @__PURE__ */ new Date()).valueOf();
|
|
81
|
-
const class_response = await classifyQuery(user_prompt, llm);
|
|
82
|
-
let ai_output_json;
|
|
83
|
-
mayLog("Time taken for classification:", formatElapsedTime(Date.now() - time1));
|
|
84
|
-
if (class_response.type == "notplot") {
|
|
85
|
-
const time2 = (/* @__PURE__ */ new Date()).valueOf();
|
|
86
|
-
const notPlotResult = await classifyNotPlot(user_prompt, llm, agentFiles, aiFilesDir);
|
|
87
|
-
mayLog("Time taken for classify2:", formatElapsedTime(Date.now() - time2));
|
|
88
|
-
if (notPlotResult.type == "html") {
|
|
89
|
-
ai_output_json = notPlotResult;
|
|
90
|
-
} else {
|
|
91
|
-
ai_output_json = {
|
|
92
|
-
type: "text",
|
|
93
|
-
text: "Your query does not appear to be related to the available data visualizations. Please try rephrasing your question."
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
} else if (class_response.type == "plot") {
|
|
97
|
-
let geneFeatures = [];
|
|
98
|
-
const genes_list = await parse_geneset_db(genedb);
|
|
99
|
-
const relevant_genes = extractGenesFromPrompt(user_prompt, genes_list);
|
|
100
|
-
if (relevant_genes.length > 0) {
|
|
101
|
-
const AmbiguousGeneMessage = determineAmbiguousGenePrompt(user_prompt, relevant_genes, dataset_json);
|
|
102
|
-
if (AmbiguousGeneMessage.length > 0) {
|
|
103
|
-
return {
|
|
104
|
-
type: "text",
|
|
105
|
-
text: AmbiguousGeneMessage
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
const geneDataTypeMessage = await classifyGeneDataType(
|
|
109
|
-
user_prompt,
|
|
110
|
-
llm,
|
|
111
|
-
relevant_genes,
|
|
112
|
-
dataset_json
|
|
113
|
-
);
|
|
114
|
-
if (typeof geneDataTypeMessage === "string" || geneDataTypeMessage instanceof String) {
|
|
115
|
-
if (geneDataTypeMessage.length > 0) {
|
|
116
|
-
return {
|
|
117
|
-
type: "text",
|
|
118
|
-
text: geneDataTypeMessage
|
|
119
|
-
};
|
|
120
|
-
} else {
|
|
121
|
-
throw "classifyGeneDataType agent returned an empty string, which is unexpected.";
|
|
122
|
-
}
|
|
123
|
-
} else if (Array.isArray(geneDataTypeMessage)) {
|
|
124
|
-
geneFeatures = geneDataTypeMessage;
|
|
125
|
-
} else {
|
|
126
|
-
throw "geneDataTypeMessage has unknown data type returned from classifyGeneDataType agent";
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
const classResult = await classifyPlotType(user_prompt, llm);
|
|
130
|
-
const dataset_db_output = await parse_dataset_db(dataset_db);
|
|
131
|
-
if (classResult == "summary") {
|
|
132
|
-
const time12 = (/* @__PURE__ */ new Date()).valueOf();
|
|
133
|
-
ai_output_json = await extract_summary_terms(
|
|
134
|
-
user_prompt,
|
|
135
|
-
llm,
|
|
136
|
-
dataset_db_output,
|
|
137
|
-
dataset_json,
|
|
138
|
-
ds,
|
|
139
|
-
testing,
|
|
140
|
-
genesetNames,
|
|
141
|
-
geneFeatures,
|
|
142
|
-
aiFilesDir
|
|
143
|
-
);
|
|
144
|
-
mayLog("Time taken for summary agent:", formatElapsedTime(Date.now() - time12));
|
|
145
|
-
} else if (classResult == "dge") {
|
|
146
|
-
const time12 = (/* @__PURE__ */ new Date()).valueOf();
|
|
147
|
-
ai_output_json = await extract_DE_search_terms_from_query(
|
|
148
|
-
user_prompt,
|
|
149
|
-
llm,
|
|
150
|
-
dataset_db_output,
|
|
151
|
-
dataset_json,
|
|
152
|
-
ds,
|
|
153
|
-
testing,
|
|
154
|
-
aiFilesDir
|
|
155
|
-
);
|
|
156
|
-
mayLog("Time taken for DE agent:", formatElapsedTime(Date.now() - time12));
|
|
157
|
-
} else if (classResult == "survival") {
|
|
158
|
-
ai_output_json = { type: "text", text: "survival agent has not been implemented yet" };
|
|
159
|
-
} else if (classResult == "matrix") {
|
|
160
|
-
const time12 = (/* @__PURE__ */ new Date()).valueOf();
|
|
161
|
-
ai_output_json = await extract_matrix_search_terms_from_query(
|
|
162
|
-
user_prompt,
|
|
163
|
-
llm,
|
|
164
|
-
dataset_db_output,
|
|
165
|
-
dataset_json,
|
|
166
|
-
ds,
|
|
167
|
-
testing,
|
|
168
|
-
genesetNames,
|
|
169
|
-
geneFeatures,
|
|
170
|
-
aiFilesDir
|
|
171
|
-
);
|
|
172
|
-
mayLog("Time taken for matrix agent:", formatElapsedTime(Date.now() - time12));
|
|
173
|
-
} else if (classResult == "samplescatter") {
|
|
174
|
-
const time12 = (/* @__PURE__ */ new Date()).valueOf();
|
|
175
|
-
ai_output_json = await extract_samplescatter_terms_from_query(
|
|
176
|
-
user_prompt,
|
|
177
|
-
llm,
|
|
178
|
-
dataset_db_output,
|
|
179
|
-
dataset_json,
|
|
180
|
-
ds,
|
|
181
|
-
testing,
|
|
182
|
-
genesetNames,
|
|
183
|
-
geneFeatures,
|
|
184
|
-
aiFilesDir
|
|
185
|
-
);
|
|
186
|
-
mayLog("Time taken for sampleScatter agent:", formatElapsedTime(Date.now() - time12));
|
|
187
|
-
} else if (classResult == "hiercluster") {
|
|
188
|
-
const time12 = (/* @__PURE__ */ new Date()).valueOf();
|
|
189
|
-
ai_output_json = await extract_hiercluster_terms_from_query(
|
|
190
|
-
user_prompt,
|
|
191
|
-
llm,
|
|
192
|
-
dataset_db_output,
|
|
193
|
-
dataset_json,
|
|
194
|
-
ds,
|
|
195
|
-
testing,
|
|
196
|
-
genesetNames,
|
|
197
|
-
geneFeatures,
|
|
198
|
-
aiFilesDir
|
|
199
|
-
);
|
|
200
|
-
mayLog("Time taken for hierCluster agent:", formatElapsedTime(Date.now() - time12));
|
|
201
|
-
} else if (classResult == "lollipop") {
|
|
202
|
-
ai_output_json = {
|
|
203
|
-
type: "text",
|
|
204
|
-
text: "This is a gene mutation prompt. But, lollipop agent has not been implemented yet"
|
|
205
|
-
};
|
|
206
|
-
} else {
|
|
207
|
-
ai_output_json = { type: "text", text: "Unknown classification value" };
|
|
208
|
-
}
|
|
209
|
-
} else {
|
|
210
|
-
ai_output_json = { type: "text", text: "Unknown classification type" };
|
|
211
|
-
}
|
|
212
|
-
return ai_output_json;
|
|
213
|
-
}
|
|
214
|
-
export {
|
|
215
|
-
api,
|
|
216
|
-
run_chat_pipeline
|
|
217
|
-
};
|