Skip to content

Commit

Permalink
Merge pull request #286 from RobokopU24/feature/explore-page
Browse files Browse the repository at this point in the history
Explore page
  • Loading branch information
Woozl authored Dec 10, 2024
2 parents 47bdef5 + cc6ca1f commit b8e663d
Show file tree
Hide file tree
Showing 13 changed files with 1,811 additions and 109 deletions.
21 changes: 21 additions & 0 deletions api_routes/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ const { handleAxiosError } = require('./utils');
const services = require('./services');
const external_apis = require('./external');

const { getDrugDiseasePairs } = require('../explore/query/drug-disease');

const samples = JSON.parse(fs.readFileSync(path.join(__dirname, './sample-query-cache.json')));

router.use('/', external_apis);
Expand Down Expand Up @@ -58,6 +60,25 @@ router.route('/quick_answer')
}
});

router.route('/explore/drug-disease')
.post(async (req, res) => {
const { sort, filters, pagination } = req.body;

if (!pagination || !Number.isInteger(pagination.offset) || !Number.isInteger(pagination.limit)) return res.status(400).json({ error: 'Missing pagination' });
if (pagination.limit < 1 || pagination.offset < 0) return res.status(400).json({ error: 'Invalid limit or offset value' });

const limit = Math.min(pagination.limit, 500);
const { offset } = pagination;

try {
const results = await getDrugDiseasePairs({ sort, filters, pagination: { limit, offset } });
return res.status(200).json(results);
} catch (error) {
console.error('Error in explore page query:', error, req.body);
return res.status(500).json({ error: 'Internal server error' });
}
});

router.route('/answer')
.post(async (req, res) => {
const { questionId, ara } = req.query;
Expand Down
Empty file added explore/README.md
Empty file.
37 changes: 37 additions & 0 deletions explore/db_utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const run = async (db, sql, params) => new Promise((res, rej) => {
db.run(sql, params, (err) => {
if (err) rej(err);
else res();
});
});

const get = async (db, sql, params) => new Promise((res, rej) => {
db.get(sql, params, (err, row) => {
if (err) rej(err);
else res(row);
});
});

const all = async (db, sql, params) => new Promise((res, rej) => {
db.all(sql, params, (err, rows) => {
if (err) rej(err);
else res(rows);
});
});

/**
* Same as normal template literal but 'sql' tag allows syntax highlighting in editor
*/
const sql = (strings, ...args) => strings.reduce((str, curr, i) => str + curr + (args[i] || ''), '');

module.exports = {
/**
* Promise-based wrappers for node-sqlite3. Attempts to mirror API, first param is db instance
*/
db_p: {
run,
get,
all,
},
sql,
};
133 changes: 133 additions & 0 deletions explore/query/drug-disease.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/* eslint-disable no-restricted-syntax */
/* eslint-disable no-use-before-define */
const path = require('path');
const sqlite3 = require('sqlite3');
const { db_p, sql } = require('../db_utils');

require('dotenv').config({ path: path.resolve(__dirname, '../../.env') });

const db = new sqlite3.Database(process.env.EXPLORE_DB);

/**
* @param {{
* sort?: {
* drug_name?: "asc" | "desc",
* disease_name?: "asc" | "desc",
* score?: "asc" | "desc",
* known?: "asc" | "desc",
* }
* filters?: {
* drug_name?: string,
* drug_id?: string,
* disease_name?: string,
* disease_id?: string,
* },
* pagination: {
* limit: number,
* offset: number,
* }
* }} params
* @returns {Promise<{
* rows: {
* drug_name: string,
* drug_id: string,
* disease_name: string,
* disease_id: string,
* score: number,
* known: number
* }[],
* num_of_results: number,
* limit: number,
* offset: number,
* }>}
*/
async function getDrugDiseasePairs({ sort, filters, pagination }) {
const { whereClause, whereParams } = buildWhereClause(filters);
const orderByClause = buildOrderByClause(sort);

const { paginationClause, paginationParams } = buildPaginationClause(pagination.limit, pagination.offset);

const params = [...whereParams, ...paginationParams];

const query = sql`\
SELECT * FROM drug_disease_pairs${whereClause}${orderByClause}${paginationClause}\
`;
const countQuery = sql`\
SELECT COUNT(*) AS total FROM drug_disease_pairs${whereClause}\
`;

return new Promise((resolve, reject) => {
db.parallelize(() => {
Promise.all([
db_p.all(db, query, params),
db_p.get(db, countQuery, whereParams),
])
.then(([rows, { total }]) => {
resolve({
rows,
num_of_results: total,
...pagination,
});
})
.catch(reject);
});
});
}

function buildWhereClause(filters) {
const whereClauses = [];
const params = [];

if (filters) {
if (filters.drug_name) {
whereClauses.push('drug_name LIKE ?');
params.push(`%${filters.drug_name}%`);
}
if (filters.drug_id) {
whereClauses.push('drug_id LIKE ?');
params.push(`%${filters.drug_id}%`);
}
if (filters.disease_name) {
whereClauses.push('disease_name LIKE ?');
params.push(`%${filters.disease_name}%`);
}
if (filters.disease_id) {
whereClauses.push('disease_id LIKE ?');
params.push(`%${filters.disease_id}%`);
}
}

const whereClause =
(whereClauses.length > 0 ? ` WHERE ${whereClauses.join(' AND ')}` : '');
return { whereClause, whereParams: params };
}

function buildOrderByClause(sort) {
let orderByClause = '';
if (sort) {
const orderClauses = [];
for (const [column, direction] of Object.entries(sort)) {
if (
['drug_name', 'disease_name', 'score', 'known'].includes(column) &&
['asc', 'desc'].includes(direction)
) {
orderClauses.push(`${column} ${direction.toUpperCase()}`);
}
}
if (orderClauses.length > 0) {
orderByClause += ` ORDER BY ${orderClauses.join(', ')}`;
}
}
return orderByClause;
}

function buildPaginationClause(limit, offset) {
return {
paginationClause: ' LIMIT ? OFFSET ?',
paginationParams: [limit, offset],
};
}

module.exports = {
getDrugDiseasePairs,
};
66 changes: 66 additions & 0 deletions explore/scripts/init-drug-disease.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/* eslint-disable no-continue */
/* eslint-disable no-console */
/* eslint-disable no-restricted-syntax */

const fs = require('fs');
const path = require('path');
const sqlite3 = require('sqlite3');
const { db_p, sql } = require('../db_utils');

require('dotenv').config({ path: path.resolve(__dirname, '../../.env') });

const input = '../data/raw/DrugtoDiseasePrediction-full-9-30-24.json';
const lowerScoreLimit = 0.5;

const db = new sqlite3.Database(process.env.EXPLORE_DB);

(async () => {
await db_p.run(db, sql`
CREATE TABLE IF NOT EXISTS drug_disease_pairs (
drug_name TEXT NOT NULL,
drug_id TEXT NOT NULL,
disease_name TEXT NOT NULL,
disease_id TEXT NOT NULL,
score REAL NOT NULL,
known BOOLEAN NOT NULL CHECK (known IN (0, 1))
)
`);
await db_p.run(db, sql`DELETE FROM drug_disease_pairs`);

const rawMaps = JSON.parse(
fs.readFileSync(path.resolve(__dirname, input), 'utf-8'),
);

await db_p.run(db, sql`BEGIN TRANSACTION`);
for (const [id, score] of Object.entries(rawMaps.Score).sort((a, b) => b[1] - a[1])) {
if (score < lowerScoreLimit) continue;

const drug = {
id: rawMaps.DrugID[id],
name: rawMaps.DrugName[id],
};
const disease = {
id: rawMaps.DiseaseID[id],
name: rawMaps.DiseaseName[id],
};
const known = rawMaps.Known[id] === '1';

const command = sql`INSERT INTO drug_disease_pairs VALUES (?, ?, ?, ?, ?, ?)`;
const params = [
drug.name,
drug.id,
disease.name,
disease.id,
score,
known ? '1' : '0',
];

try {
db_p.run(db, command, params);
} catch (err) {
console.error('Error:', err, 'Params:', params);
}
}

await db_p.run(db, sql`COMMIT`);
})();
Loading

0 comments on commit b8e663d

Please sign in to comment.