Skip to content

Commit

Permalink
Merge pull request #221 from bcgsc/release/v7.12.0
Browse files Browse the repository at this point in the history
Release/v7.12.0
  • Loading branch information
Nithriel authored Jan 27, 2023
2 parents f5be276 + 587331c commit c66f0a4
Show file tree
Hide file tree
Showing 10 changed files with 690 additions and 253 deletions.
25 changes: 25 additions & 0 deletions app/models/germlineSmallMutation/variants.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,31 @@ module.exports = (sequelize, Sq) => {
type: Sq.TEXT,
allowNull: true,
},
cglReviewResult: {
name: 'cglReviewResult',
field: 'cgl_review_result',
type: Sq.ENUM(['pathogenic', 'likely pathogenic', 'VUS', 'likely benign', 'benign']),
},
returnedToClinician: {
name: 'returnedToClinician',
field: 'returned_to_clinician',
type: Sq.ENUM(['yes', 'no']),
},
referralHcp: {
name: 'referralHcp',
field: 'referral_hcp',
type: Sq.ENUM(['yes', 'no']),
},
knownToHcp: {
name: 'knownToHcp',
field: 'known_to_hcp',
type: Sq.ENUM(['yes', 'no']),
},
reasonNoHcpReferral: {
name: 'reasonNoHcpReferral',
field: 'reason_no_hcp_referral',
type: Sq.TEXT,
},
}, {
...DEFAULT_OPTIONS,
tableName: 'germline_small_mutations_variant',
Expand Down
2 changes: 2 additions & 0 deletions app/routes/report/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const patientInformation = require('./patientInformation');
const images = require('./image');
const msi = require('./msi');
const reportUser = require('./reportUser');
const variants = require('./variants');

const router = express.Router({mergeParams: true});

Expand Down Expand Up @@ -56,5 +57,6 @@ router.use('/genes', gene);
router.use('/patient-information', patientInformation);
router.use('/msi', msi);
router.use('/user', reportUser);
router.use('/variants', variants);

module.exports = router;
29 changes: 2 additions & 27 deletions app/routes/report/kbMatches.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const HTTP_STATUS = require('http-status-codes');
const express = require('express');
const {Op, literal} = require('sequelize');
const {Op} = require('sequelize');

const router = express.Router({mergeParams: true});

Expand Down Expand Up @@ -59,7 +59,7 @@ router.route('/:kbMatch([A-z0-9-]{36})')

router.route('/')
.get(async (req, res) => {
const {query: {matchedCancer, approvedTherapy, category, rapidTable}} = req;
const {query: {matchedCancer, approvedTherapy, category}} = req;

// Check cache
const key = generateKey(`/reports/${req.report.ident}/kb-matches`, req.query);
Expand All @@ -76,37 +76,12 @@ router.route('/')
}

try {
const therapeuticAssociationFilter = {
[Op.or]: [{iprEvidenceLevel: ['IPR-A', 'IPR-B']}],
category: 'therapeutic',
matchedCancer: true,
variantType: {[Op.is]: literal('distinct from \'exp\'')},
};

// PSQL natively ignores null on equal checks.
// Literal is used in order to accomodate NULL rows.
const cancerRelevanceFilter = {
[Op.not]: {
[Op.or]: [
{iprEvidenceLevel: {[Op.is]: literal('not distinct from \'IPR-A\'')}},
{iprEvidenceLevel: {[Op.is]: literal('not distinct from \'IPR-B\'')}},
],
category: 'therapeutic',
matchedCancer: true,
},
variantType: {[Op.is]: literal('distinct from \'exp\'')},
};

const results = await db.models.kbMatches.scope('public').findAll({
where: {
reportId: req.report.id,
...((category) ? {category: {[Op.in]: category.split(',')}} : {}),
...((typeof matchedCancer === 'boolean') ? {matchedCancer} : {}),
...((typeof approvedTherapy === 'boolean') ? {approvedTherapy} : {}),
...((rapidTable === 'therapeuticAssociation')
? therapeuticAssociationFilter : {}),
...((rapidTable === 'cancerRelevance')
? cancerRelevanceFilter : {}),
},
order: [['variantType', 'ASC'], ['variantId', 'ASC']],
});
Expand Down
184 changes: 184 additions & 0 deletions app/routes/report/variants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
const HTTP_STATUS = require('http-status-codes');
const express = require('express');

const router = express.Router({mergeParams: true});

const {Op, literal} = require('sequelize');
const db = require('../../models');
const logger = require('../../log');

const {KB_PIVOT_MAPPING} = require('../../constants');

const KBMATCHEXCLUDE = ['id', 'reportId', 'variantId', 'deletedAt', 'updatedBy'];
const getVariants = async (tableName, variantType, reportId) => {
return db.models[tableName].scope('extended').findAll({
order: [['id', 'ASC']],
attributes: {
include: [[literal(`'${variantType}'`), 'variantType']],
},
where: {
reportId,
},
include: [
{
model: db.models.kbMatches,
attributes: {exclude: KBMATCHEXCLUDE},
},
],
});
};

const therapeuticAssociationFilter = {
id: {[Op.ne]: null},
[Op.or]: [{iprEvidenceLevel: ['IPR-A', 'IPR-B']}],
category: 'therapeutic',
matchedCancer: true,
variantType: {[Op.is]: literal('distinct from \'exp\'')},
};

// PSQL natively ignores null on equal checks.
// Literal is used in order to accomodate NULL rows.
const cancerRelevanceFilter = {
id: {[Op.ne]: null},
[Op.not]: {
[Op.or]: [
{iprEvidenceLevel: {[Op.is]: literal('not distinct from \'IPR-A\'')}},
{iprEvidenceLevel: {[Op.is]: literal('not distinct from \'IPR-B\'')}},
],
category: 'therapeutic',
matchedCancer: true,
},
variantType: {[Op.is]: literal('distinct from \'exp\'')},
};

const unknownSignificanceIncludes = ['mut'];

const unknownSignificanceGeneFilter = {
[Op.or]: [{oncogene: true}, {tumourSuppressor: true}],
};

const getRapidReportVariants = async (tableName, variantType, reportId, rapidTable) => {
const therapeuticAssociationResults = await db.models[tableName].scope('extended').findAll({
order: [['id', 'ASC']],
attributes: {
include: [[literal(`'${variantType}'`), 'variantType']],
},
where: {
reportId,
},
include: [
{
model: db.models.kbMatches,
attributes: {exclude: KBMATCHEXCLUDE},
where: {...therapeuticAssociationFilter},
},
],
});

if (rapidTable === 'therapeuticAssociation') {
return therapeuticAssociationResults;
}

const cancerRelevanceResultsFiltered = [];
const cancerRelevanceResults = await db.models[tableName].scope('extended').findAll({
order: [['id', 'ASC']],
attributes: {
include: [[literal(`'${variantType}'`), 'variantType']],
},
where: {
reportId,
},
include: [
{
model: db.models.kbMatches,
where: {...cancerRelevanceFilter},
attributes: {exclude: KBMATCHEXCLUDE},
},
],
});

for (const row of cancerRelevanceResults) {
if (!(therapeuticAssociationResults.find(
(e) => {return e.ident === row.ident;},
))) {
cancerRelevanceResultsFiltered.push(row);
}
}

if (rapidTable === 'cancerRelevance') {
return cancerRelevanceResultsFiltered;
}

const unknownSignificanceResultsFiltered = [];
let unknownSignificanceResults = [];

if (unknownSignificanceIncludes.includes(variantType)) {
unknownSignificanceResults = await db.models[tableName].scope('extended').findAll({
order: [['id', 'ASC']],
attributes: {
include: [[literal(`'${variantType}'`), 'variantType']],
},
where: {
reportId,
},
include: [
{
model: db.models.kbMatches,
attributes: {exclude: KBMATCHEXCLUDE},
},
{
model: db.models.genes.scope('minimal'),
as: 'gene',
where: unknownSignificanceGeneFilter,
},
],
});
}

for (const row of unknownSignificanceResults) {
if (!(therapeuticAssociationResults.find(
(e) => {return e.ident === row.ident;},
)) && !(cancerRelevanceResultsFiltered.find(
(e) => {return e.ident === row.ident;},
))) {
unknownSignificanceResultsFiltered.push(row);
}
}

return unknownSignificanceResultsFiltered;
};

// Routing for Alteration
router.route('/')
.get(async (req, res) => {
// Get all variants for this report
// Cache was removed from this endpoint due to requiring multiple tables,
// increasing the chance of retrieving outdated data
const {query: {rapidTable}} = req;

try {
const variantTypes = Object.keys(KB_PIVOT_MAPPING);
const variantsArray = [];

for (const variantType of variantTypes) {
const tableName = KB_PIVOT_MAPPING[variantType];

if (rapidTable) {
variantsArray.push(await getRapidReportVariants(tableName, variantType, req.report.id, rapidTable));
} else {
variantsArray.push(await getVariants(tableName, variantType, req.report.id));
}
}

const results = variantsArray.flat(1);

return res.json(results);
} catch (error) {
logger.error(`Unable to retrieve variants ${error}`);
return res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({
error: {message: 'Unable to retrieve variants'},
});
}
});

module.exports = router;
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const GSM_TABLE = 'germline_small_mutations_variant';

module.exports = {
up: async (queryInterface, Sq) => {
return queryInterface.sequelize.transaction(async (transaction) => {
await queryInterface.addColumn(
GSM_TABLE,
'cgl_review_result',
{type: Sq.ENUM(['pathogenic', 'likely pathogenic', 'VUS', 'likely benign', 'benign']),
defaultValue: null},
{transaction},
);
await queryInterface.addColumn(
GSM_TABLE,
'returned_to_clinician',
{type: Sq.ENUM(['yes', 'no']),
defaultValue: null},
{transaction},
);
await queryInterface.addColumn(
GSM_TABLE,
'referral_hcp',
{type: Sq.ENUM(['yes', 'no']),
defaultValue: null},
{transaction},
);
await queryInterface.addColumn(
GSM_TABLE,
'known_to_hcp',
{type: Sq.ENUM(['yes', 'no']),
defaultValue: null},
{transaction},
);
await queryInterface.addColumn(
GSM_TABLE,
'reason_no_hcp_referral',
{type: Sq.TEXT,
defaultValue: null},
{transaction},
);
});
},

down: async () => {
throw new Error('Not Implemented!');
},
};
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"private": true,
"name": "ipr-api",
"version": "7.11.2",
"version": "7.12.0",
"description": "Integrated Pipeline Reports API",
"main": "bin/server.js",
"scripts": {
Expand Down
Loading

0 comments on commit c66f0a4

Please sign in to comment.