@sjcrh/proteinpaint-server 2.180.0 → 2.180.1

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.
@@ -438,7 +438,7 @@ function termdb_test_default() {
438
438
  jsonFile: "files/hg38/TermdbTest/trackLst/facet.json",
439
439
  activeTracks: ["bw 1", "bed 1"]
440
440
  },
441
- chat: { aifiles: "./proteinpaint/server/dataset/ai/termdb.test.json" }
441
+ chat: {}
442
442
  }
443
443
  };
444
444
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjcrh/proteinpaint-server",
3
- "version": "2.180.0",
3
+ "version": "2.180.1",
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",
@@ -63,10 +63,10 @@
63
63
  "dependencies": {
64
64
  "@sjcrh/augen": "2.143.0",
65
65
  "@sjcrh/proteinpaint-python": "2.179.0",
66
- "@sjcrh/proteinpaint-r": "2.178.0",
67
- "@sjcrh/proteinpaint-rust": "2.180.0",
68
- "@sjcrh/proteinpaint-shared": "2.180.0",
69
- "@sjcrh/proteinpaint-types": "2.180.0",
66
+ "@sjcrh/proteinpaint-r": "2.180.1-0",
67
+ "@sjcrh/proteinpaint-rust": "2.180.1-0",
68
+ "@sjcrh/proteinpaint-shared": "2.180.1",
69
+ "@sjcrh/proteinpaint-types": "2.180.1",
70
70
  "@types/express": "^5.0.0",
71
71
  "@types/express-session": "^1.18.1",
72
72
  "better-sqlite3": "^12.4.1",
@@ -48,23 +48,11 @@ async function trigger_getcategories(q, res, tdb, ds) {
48
48
  const data = await getData(arg, ds);
49
49
  if (data.error) throw data.error;
50
50
  const [lst, orderedLabels] = getCategories(data, q, ds, $id);
51
- const allowedValues = tdb.getRestrictedValues?.(q.__protected__?.clientAuthResult, q.tw.term.id);
52
- const filtered = filterCategoriesByAllowedValues(lst, orderedLabels, allowedValues);
53
51
  res.send({
54
- lst: filtered.lst,
55
- orderedLabels: filtered.orderedLabels
52
+ lst,
53
+ orderedLabels
56
54
  });
57
55
  }
58
- function filterCategoriesByAllowedValues(lst, orderedLabels, allowedValues) {
59
- if (!allowedValues) {
60
- return { lst, orderedLabels };
61
- }
62
- const allowedValuesSet = new Set(allowedValues.map((v) => String(v)));
63
- const filteredLst = lst.filter((item) => allowedValuesSet.has(String(item.key)));
64
- const allowedLabels = new Set(filteredLst.map((item) => item.label));
65
- const filteredOrderedLabels = orderedLabels.filter((label) => allowedLabels.has(label));
66
- return { lst: filteredLst, orderedLabels: filteredOrderedLabels };
67
- }
68
56
  function getCategories(data, q, ds, $id) {
69
57
  const lst = [];
70
58
  if (q.tw.term.type == "geneVariant" && q.tw.q.type != "predefined-groupset" && q.tw.q.type != "custom-groupset") {
@@ -145,6 +133,5 @@ function getCategories(data, q, ds, $id) {
145
133
  }
146
134
  export {
147
135
  api,
148
- filterCategoriesByAllowedValues,
149
136
  getCategories
150
137
  };
@@ -2,7 +2,6 @@ import { ChatPayload } from "#types/checkers";
2
2
  import { classifyQuery } from "./chat/classify1.ts";
3
3
  import { classifyNotPlot } from "./chat/classify2.ts";
4
4
  import { classifyPlotType } from "./chat/plot.ts";
5
- import { readJSONFile } from "./chat/utils.ts";
6
5
  import { extract_DE_search_terms_from_query } from "./chat/DEagent.ts";
7
6
  import { determineAmbiguousGenePrompt } from "./chat/ambiguousgeneagent.ts";
8
7
  import { extract_summary_terms } from "./chat/summaryagent.ts";
@@ -14,6 +13,9 @@ import { extractGenesFromPrompt, parse_dataset_db, parse_geneset_db, getGenesetN
14
13
  import serverconfig from "../src/serverconfig.js";
15
14
  import { mayLog } from "#src/helpers.ts";
16
15
  import { formatElapsedTime } from "#shared";
16
+ import path from "path";
17
+ import fs from "fs";
18
+ import { readJSONFile } from "./chat/utils.ts";
17
19
  const api = {
18
20
  endpoint: "termdb/chat2",
19
21
  methods: {
@@ -35,8 +37,14 @@ function init({ genomes }) {
35
37
  if (!g) throw "invalid genome";
36
38
  const ds = g.datasets?.[q.dslabel];
37
39
  if (!ds) throw "invalid dslabel";
38
- if (!ds?.queries?.chat?.aifiles) {
39
- throw "aifiles are missing for chatbot to work";
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;
40
48
  }
41
49
  const llm = serverconfig.llm;
42
50
  if (!llm) throw "serverconfig.llm is not configured";
@@ -45,18 +53,18 @@ function init({ genomes }) {
45
53
  }
46
54
  const dataset_db = serverconfig.tpmasterdir + "/" + ds.cohort.db.file;
47
55
  const genedb = serverconfig.tpmasterdir + "/" + g.genedb.dbfile;
48
- const dataset_json = await readJSONFile(ds?.queries?.chat?.aifiles);
49
56
  const testing = false;
50
57
  const genesetNames = getGenesetNames(g);
51
58
  const ai_output_json = await run_chat_pipeline(
52
59
  q.prompt,
53
60
  llm,
54
- dataset_json,
55
61
  testing,
56
62
  dataset_db,
57
63
  genedb,
58
64
  ds,
59
- genesetNames
65
+ genesetNames,
66
+ agentFiles,
67
+ aiFilesDir
60
68
  );
61
69
  res.send(ai_output_json);
62
70
  } catch (e) {
@@ -65,14 +73,17 @@ function init({ genomes }) {
65
73
  }
66
74
  };
67
75
  }
68
- async function run_chat_pipeline(user_prompt, llm, dataset_json, testing, dataset_db, genedb, ds, genesetNames = []) {
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"));
69
80
  const time1 = (/* @__PURE__ */ new Date()).valueOf();
70
81
  const class_response = await classifyQuery(user_prompt, llm);
71
82
  let ai_output_json;
72
83
  mayLog("Time taken for classification:", formatElapsedTime(Date.now() - time1));
73
84
  if (class_response.type == "notplot") {
74
85
  const time2 = (/* @__PURE__ */ new Date()).valueOf();
75
- const notPlotResult = await classifyNotPlot(user_prompt, llm, dataset_json);
86
+ const notPlotResult = await classifyNotPlot(user_prompt, llm, agentFiles, aiFilesDir);
76
87
  mayLog("Time taken for classify2:", formatElapsedTime(Date.now() - time2));
77
88
  if (notPlotResult.type == "html") {
78
89
  ai_output_json = notPlotResult;
@@ -127,7 +138,8 @@ async function run_chat_pipeline(user_prompt, llm, dataset_json, testing, datase
127
138
  ds,
128
139
  testing,
129
140
  genesetNames,
130
- geneFeatures
141
+ geneFeatures,
142
+ aiFilesDir
131
143
  );
132
144
  mayLog("Time taken for summary agent:", formatElapsedTime(Date.now() - time12));
133
145
  } else if (classResult == "dge") {
@@ -138,7 +150,8 @@ async function run_chat_pipeline(user_prompt, llm, dataset_json, testing, datase
138
150
  dataset_db_output,
139
151
  dataset_json,
140
152
  ds,
141
- testing
153
+ testing,
154
+ aiFilesDir
142
155
  );
143
156
  mayLog("Time taken for DE agent:", formatElapsedTime(Date.now() - time12));
144
157
  } else if (classResult == "survival") {
@@ -153,7 +166,8 @@ async function run_chat_pipeline(user_prompt, llm, dataset_json, testing, datase
153
166
  ds,
154
167
  testing,
155
168
  genesetNames,
156
- geneFeatures
169
+ geneFeatures,
170
+ aiFilesDir
157
171
  );
158
172
  mayLog("Time taken for matrix agent:", formatElapsedTime(Date.now() - time12));
159
173
  } else if (classResult == "samplescatter") {
@@ -166,7 +180,8 @@ async function run_chat_pipeline(user_prompt, llm, dataset_json, testing, datase
166
180
  ds,
167
181
  testing,
168
182
  genesetNames,
169
- geneFeatures
183
+ geneFeatures,
184
+ aiFilesDir
170
185
  );
171
186
  mayLog("Time taken for sampleScatter agent:", formatElapsedTime(Date.now() - time12));
172
187
  } else if (classResult == "hiercluster") {
@@ -179,7 +194,8 @@ async function run_chat_pipeline(user_prompt, llm, dataset_json, testing, datase
179
194
  ds,
180
195
  testing,
181
196
  genesetNames,
182
- geneFeatures
197
+ geneFeatures,
198
+ aiFilesDir
183
199
  );
184
200
  mayLog("Time taken for hierCluster agent:", formatElapsedTime(Date.now() - time12));
185
201
  } else if (classResult == "lollipop") {
@@ -7,8 +7,7 @@ import serverconfig from "#src/serverconfig.js";
7
7
  import { gdc_validate_query_geneExpression } from "#src/mds3.gdc.js";
8
8
  import { mayLimitSamples } from "#src/mds3.filter.js";
9
9
  import { clusterMethodLst, distanceMethodLst } from "#shared/clustering.js";
10
- import { TermTypes, NUMERIC_DICTIONARY_TERM } from "#shared/terms.js";
11
- import { getData } from "#src/termdb.matrix.js";
10
+ import { TermTypes, PROTEOME_ABUNDANCE } from "#shared/terms.js";
12
11
  import { termType2label } from "#shared/terms.js";
13
12
  import { formatElapsedTime } from "#shared/time.js";
14
13
  const api = {
@@ -35,16 +34,16 @@ function init({ genomes }) {
35
34
  if (!ds) throw "invalid dataset name";
36
35
  if (ds.label === "GDC" && !ds.__gdc?.doneCaching)
37
36
  throw "The server has not finished caching the case IDs: try again in about 2 minutes.";
38
- if ([TermTypes.GENE_EXPRESSION, TermTypes.METABOLITE_INTENSITY, NUMERIC_DICTIONARY_TERM].includes(q.dataType)) {
39
- if (!ds.queries?.[q.dataType] && q.dataType !== NUMERIC_DICTIONARY_TERM)
40
- throw `no ${q.dataType} data on this dataset`;
37
+ if ([TermTypes.GENE_EXPRESSION, TermTypes.METABOLITE_INTENSITY].includes(q.dataType)) {
38
+ if (!ds.queries?.[q.dataType]) throw `no ${q.dataType} data on this dataset`;
41
39
  if (!q.terms) throw `missing gene list`;
42
40
  if (!Array.isArray(q.terms)) throw `gene list is not an array`;
43
41
  if (q.terms.length < 3)
44
42
  throw `A minimum of three genes is required for clustering. Please refresh this page to clear this error.`;
45
43
  result = await getResult(q, ds);
46
- } else if (TermTypes.WHOLE_PROTEOME_ABUNDANCE == q.dataType) {
47
- if (!ds.queries?.proteome?.whole) throw `no ${TermTypes.WHOLE_PROTEOME_ABUNDANCE} data on this dataset`;
44
+ } else if (PROTEOME_ABUNDANCE == q.dataType) {
45
+ const proteomeQuery = ds.queries?.proteome;
46
+ if (!proteomeQuery?.get) throw `no ${TermTypes.PROTEOME_ABUNDANCE} data getter on this dataset`;
48
47
  if (!q.terms) throw `missing gene list`;
49
48
  if (!Array.isArray(q.terms)) throw `gene list is not an array`;
50
49
  if (q.terms.length < 3)
@@ -71,12 +70,9 @@ async function getResult(q, ds) {
71
70
  _q.__abortSignal = q.__abortSignal;
72
71
  }
73
72
  let term2sample2value, byTermId, bySampleId, skippedSexChrGenes;
74
- if (q.dataType == NUMERIC_DICTIONARY_TERM) {
73
+ if (q.dataType == PROTEOME_ABUNDANCE) {
75
74
  ;
76
- ({ term2sample2value, byTermId, bySampleId } = await getNumericDictTermAnnotation(q, ds));
77
- } else if (q.dataType == TermTypes.WHOLE_PROTEOME_ABUNDANCE) {
78
- ;
79
- ({ term2sample2value, byTermId, bySampleId, skippedSexChrGenes } = await ds.queries.proteome.whole.get(_q, ds));
75
+ ({ term2sample2value, byTermId, bySampleId, skippedSexChrGenes } = await ds.queries.proteome.get(_q));
80
76
  } else {
81
77
  ;
82
78
  ({ term2sample2value, byTermId, bySampleId, skippedSexChrGenes } = await ds.queries[q.dataType].get(_q, ds));
@@ -113,28 +109,6 @@ async function getResult(q, ds) {
113
109
  if (removedHierClusterTerms.length) result.removedHierClusterTerms = removedHierClusterTerms;
114
110
  return result;
115
111
  }
116
- async function getNumericDictTermAnnotation(q, ds) {
117
- const getDataArgs = {
118
- // TODO: figure out when term is not a termwrapper
119
- terms: q.terms.map((tw) => tw.term ? tw : { term: tw, q: { mode: "continuous" } }),
120
- filter: q.filter,
121
- filter0: q.filter0,
122
- __protected__: q.__protected__
123
- };
124
- const data = await getData(getDataArgs, ds);
125
- const term2sample2value = /* @__PURE__ */ new Map();
126
- for (const [key, sampleData] of Object.entries(data.samples)) {
127
- for (const [term, value] of Object.entries(sampleData)) {
128
- if (term !== "sample") {
129
- if (!term2sample2value.has(term)) {
130
- term2sample2value.set(term, {});
131
- }
132
- term2sample2value.get(term)[key] = value.value;
133
- }
134
- }
135
- }
136
- return { term2sample2value, byTermId: data.refs.byTermId, bySampleId: data.refs.bySampleId };
137
- }
138
112
  async function doClustering(data, q, numCases = 1e3) {
139
113
  const sampleSet = /* @__PURE__ */ new Set();
140
114
  let firstTerm = true;
@@ -227,13 +201,14 @@ async function validate_query_geneExpression(ds, genome) {
227
201
  }
228
202
  throw "unknown queries.geneExpression.src";
229
203
  }
230
- async function queryHDF5(hdf5_file, query) {
231
- const jsonInput = JSON.stringify({
204
+ async function queryHDF5(hdf5_file, query, read_mode) {
205
+ const input = {
232
206
  hdf5_file,
233
207
  query
234
- });
208
+ };
209
+ if (read_mode) input.read_mode = read_mode;
235
210
  try {
236
- const result = await run_rust("readH5", jsonInput);
211
+ const result = await run_rust("readH5", JSON.stringify(input));
237
212
  if (!result || result.length === 0) {
238
213
  throw new Error("Failed to retrieve expression data: Empty or missing response");
239
214
  }
@@ -254,10 +229,16 @@ async function validateNative(q, ds) {
254
229
  if (!vr.samples?.length) throw "HDF5 file has no samples, please check file.";
255
230
  for (const sn of vr.samples) {
256
231
  const si = ds.cohort.termdb.q.sampleName2id(sn);
257
- if (si == void 0) throw `unknown sample ${sn} from HDF5 ${q.file}`;
232
+ if (si == void 0) {
233
+ if (ds.cohort.db) {
234
+ throw `unknown sample ${sn} from HDF5 ${q.file}`;
235
+ } else {
236
+ continue;
237
+ }
238
+ }
258
239
  q.samples.push(si);
259
240
  }
260
- console.log(`${ds.label}: geneExpression HDF5 file validated. Format: ${vr.format}, Samples:`, vr.samples.length);
241
+ console.log(`${ds.label}: geneExpression HDF5 file validated. Format: ${vr.format}, Samples:`, q.samples.length);
261
242
  } catch (error) {
262
243
  throw `${ds.label}: Failed to validate geneExpression HDF5 file: ${error}`;
263
244
  }
@@ -270,11 +251,19 @@ async function validateNative(q, ds) {
270
251
  const samples = q.samples || [];
271
252
  if (limitSamples) {
272
253
  for (const sid of limitSamples) {
273
- bySampleId[sid] = { label: ds.cohort.termdb.q.id2sampleName(sid) };
254
+ if (ds.cohort?.termdb?.q?.id2sampleRefs) {
255
+ bySampleId[sid] = ds.cohort.termdb.q.id2sampleRefs(sid);
256
+ } else {
257
+ bySampleId[sid] = { label: ds.cohort.termdb.q.id2sampleName(sid) };
258
+ }
274
259
  }
275
260
  } else {
276
261
  for (const sid of samples) {
277
- bySampleId[sid] = { label: ds.cohort.termdb.q.id2sampleName(sid) };
262
+ if (ds.cohort?.termdb?.q?.id2sampleRefs) {
263
+ bySampleId[sid] = ds.cohort.termdb.q.id2sampleRefs(sid);
264
+ } else {
265
+ bySampleId[sid] = { label: ds.cohort.termdb.q.id2sampleName(sid) };
266
+ }
278
267
  }
279
268
  }
280
269
  const term2sample2value = /* @__PURE__ */ new Map();
@@ -290,7 +279,8 @@ async function validateNative(q, ds) {
290
279
  return { term2sample2value, byTermId };
291
280
  }
292
281
  const time1 = Date.now();
293
- const geneData = JSON.parse(await queryHDF5(q.file, geneNames));
282
+ const readMode = param.dslabel == "MMRF" ? "bulk" : null;
283
+ const geneData = JSON.parse(await queryHDF5(q.file, geneNames, readMode));
294
284
  console.log("Time taken to run gene query:", formatElapsedTime(Date.now() - time1));
295
285
  const genesData = geneData.query_output || {};
296
286
  if (!genesData) throw "No expression data returned from HDF5 query";
@@ -6,7 +6,7 @@ import {
6
6
  SINGLECELL_CELLTYPE,
7
7
  GENE_EXPRESSION,
8
8
  METABOLITE_INTENSITY,
9
- WHOLE_PROTEOME_ABUNDANCE,
9
+ PROTEOME_ABUNDANCE,
10
10
  SINGLECELL_GENE_EXPRESSION,
11
11
  DNA_METHYLATION,
12
12
  SSGSEA
@@ -51,7 +51,6 @@ function make(q, req, res, ds, genome) {
51
51
  dataDownloadCatch: tdb.dataDownloadCatch,
52
52
  matrix: tdb.matrix,
53
53
  hierCluster: tdb.hierCluster,
54
- numericDictTermCluster: tdb.numericDictTermCluster,
55
54
  mclass: tdb.mclass,
56
55
  alwaysRefillCategoricalTermValues: tdb.alwaysRefillCategoricalTermValues,
57
56
  isGeneSetTermdb: tdb.isGeneSetTermdb,
@@ -89,7 +88,6 @@ function make(q, req, res, ds, genome) {
89
88
  if (tdb.maxAnnoTermsPerClientRequest) c.maxAnnoTermsPerClientRequest = tdb.maxAnnoTermsPerClientRequest;
90
89
  addRestrictAncestries(c, tdb);
91
90
  addMatrixplots(c, ds);
92
- addMutationSignatureplots(c, ds);
93
91
  addNonDictionaryQueries(c, ds, genome);
94
92
  c.requiredAuth = authApi.getRequiredCredForDsEmbedder(q.dslabel, q.embedder);
95
93
  const info = authApi.getNonsensitiveInfo(req);
@@ -132,15 +130,6 @@ function addMatrixplots(c, ds) {
132
130
  return { name: p.name };
133
131
  });
134
132
  }
135
- function addMutationSignatureplots(c, ds) {
136
- const mutationSignatureplots = ds.cohort.termdb.termCollections?.find(
137
- (tc) => tc.name == "Mutation Signature" && tc.type === "numeric"
138
- )?.plots;
139
- if (!mutationSignatureplots) return;
140
- c.mutationSignatureplots = mutationSignatureplots.map((p) => {
141
- return { name: p.name };
142
- });
143
- }
144
133
  function addNonDictionaryQueries(c, ds, genome) {
145
134
  const q = ds.queries;
146
135
  if (!q) return;
@@ -198,6 +187,26 @@ function addNonDictionaryQueries(c, ds, genome) {
198
187
  if (q.geneExpression) {
199
188
  q2.geneExpression = { unit: q.geneExpression.unit };
200
189
  }
190
+ if (q.proteome) {
191
+ q2.proteome = {};
192
+ if (q.proteome.assays) {
193
+ q2.proteome.assays = {};
194
+ for (const assay in q.proteome.assays) {
195
+ q2.proteome.assays[assay] = {};
196
+ if (q.proteome.assays[assay].cohorts) {
197
+ q2.proteome.assays[assay].cohorts = {};
198
+ for (const cohort in q.proteome.assays[assay].cohorts) {
199
+ q2.proteome.assays[assay].cohorts[cohort] = {};
200
+ if ("filter" in q.proteome.assays[assay].cohorts[cohort]) {
201
+ q2.proteome.assays[assay].cohorts[cohort].filter = JSON.parse(
202
+ JSON.stringify(q.proteome.assays[assay].cohorts[cohort].filter)
203
+ );
204
+ }
205
+ }
206
+ }
207
+ }
208
+ }
209
+ }
201
210
  if (q.dnaMethylation) {
202
211
  q2.dnaMethylation = { unit: q.dnaMethylation.unit };
203
212
  if (q.dnaMethylation.promoter) {
@@ -284,7 +293,7 @@ function getAllowedTermTypes(ds) {
284
293
  }
285
294
  if (ds.queries?.geneExpression) typeSet.add(GENE_EXPRESSION);
286
295
  if (ds.queries?.metaboliteIntensity) typeSet.add(METABOLITE_INTENSITY);
287
- if (ds.queries?.proteome?.whole) typeSet.add(WHOLE_PROTEOME_ABUNDANCE);
296
+ if (ds.queries?.proteome?.assays) typeSet.add(PROTEOME_ABUNDANCE);
288
297
  if (ds.queries?.ssGSEA) typeSet.add(SSGSEA);
289
298
  if (ds.queries?.dnaMethylation) typeSet.add(DNA_METHYLATION);
290
299
  if (ds.queries?.singleCell) {
@@ -40,20 +40,11 @@ async function getFilters(query, ds) {
40
40
  );
41
41
  const tw2List = {};
42
42
  for (const tw of query.terms) {
43
- let values = getList(samplesPerFilter, filtersData, tw, query.showAll);
44
- const allowedValues = ds.cohort.termdb.getRestrictedValues?.(query.__protected__.clientAuthResult, tw.term.id);
45
- values = filterByAllowedValues(values, allowedValues);
43
+ const values = getList(samplesPerFilter, filtersData, tw, query.showAll);
46
44
  tw2List[tw.term.id] = values;
47
45
  }
48
46
  return { ...tw2List };
49
47
  }
50
- function filterByAllowedValues(values, allowedValues) {
51
- if (!allowedValues) {
52
- return values;
53
- }
54
- const allowedValuesSet = new Set(allowedValues.map((v) => String(v)));
55
- return values.filter((v) => v.value === "" || allowedValuesSet.has(String(v.value)));
56
- }
57
48
  async function getSamplesPerFilter(q, ds) {
58
49
  q.ds = ds;
59
50
  const samples = {};
@@ -94,6 +85,5 @@ function getList(samplesPerFilter, filtersData, tw, showAll) {
94
85
  return filteredValues;
95
86
  }
96
87
  export {
97
- api,
98
- filterByAllowedValues
88
+ api
99
89
  };