Skip to content

Commit

Permalink
Merge pull request #604 from hpi-dhc/issue/603-contract-by-phenotype-…
Browse files Browse the repository at this point in the history
…first

Issue/603 contract by phenotype first
  • Loading branch information
tamslo committed May 2, 2023
2 parents 0e7399b + 24e3b11 commit f8a1a90
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 59 deletions.
12 changes: 12 additions & 0 deletions anni/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Next.js: debug server-side",
"type": "node-terminal",
"request": "launch",
"command": "npm run dev"
}
]
}

121 changes: 72 additions & 49 deletions anni/src/database/helpers/cpic-constructors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CpicRecommendation } from '../../common/cpic-api';
import { IDrug_Any } from '../models/Drug';
import { IGuideline_Any } from '../models/Guideline';
import { IGuideline_Any, IExternalData } from '../models/Guideline';

function guidelineFromRecommendation(
recommendation: CpicRecommendation,
Expand Down Expand Up @@ -43,12 +43,11 @@ function guidelineFromRecommendation(

// used to merge guidelines with equal information (e.g. when the same
// guideline is used for multiple phenotypes)
function guidelineKey(recommendation: CpicRecommendation) {
function informationKey(externalData: IExternalData): string {
return [
recommendation.drugid,
recommendation.comments ?? '',
recommendation.drugrecommendation,
...Object.entries(recommendation.implications).map(
externalData.comments ?? '',
externalData.recommendation,
...Object.entries(externalData.implications).map(
([gene, implication]) => `${gene}${implication}`,
),
].join('');
Expand All @@ -60,7 +59,7 @@ function guidelineKey(recommendation: CpicRecommendation) {
// lookupkeys match to the same phenotye, as it often is the case for
// activity scores)
function phenotypeKey(guideline: IGuideline_Any): string {
return Object.keys(guideline.lookupkey)
return Object.keys(guideline.phenotypes)
.map((gene) => `${gene}${guideline.phenotypes[gene]}`)
.join('');
}
Expand All @@ -78,6 +77,65 @@ function drugFromRecommendation(recommendation: CpicRecommendation): IDrug_Any {
};
}

// initially (before contracting) guideline.externalData and values
// (phenotype descriptions) of guideline.lookupkey should be of length
// 1, as we set it this way ourselves in guidelineFromRecommendation
function ensureInitialGuidelineStructure(guideline: IGuideline_Any) {
if (
guideline.externalData.length != 1 ||
!Object.values(guideline.lookupkey).every((value) => value.length == 1)
) {
throw Error('Expected different initial guideline data structure');
}
}

// merge same-phenotype guidelines
function contractByPhenotype(
drugsWithGuidelines: Array<DrugWithGuidelines>,
): Array<DrugWithGuidelines> {
return drugsWithGuidelines.map(({ drug, guidelines }) => {
const phenotypeMap = new Map<string, IGuideline_Any>();
guidelines.forEach((guideline) => {
const key = phenotypeKey(guideline);
const existingGuideline = phenotypeMap.get(key);
if (existingGuideline) {
// ensure that we don't miss information when
// getting only first index from externalData
// and lookupkey[gene]
ensureInitialGuidelineStructure(guideline);
existingGuideline.externalData.push(guideline.externalData[0]);
Object.keys(existingGuideline.lookupkey).forEach((gene) => {
existingGuideline.lookupkey[gene].push(
guideline.lookupkey[gene][0],
);
});
} else {
phenotypeMap.set(key, guideline);
}
});
return { drug, guidelines: Array.from(phenotypeMap.values()) };
});
}

// merge same-information guidelines
function contractByInformation(
drugsWithGuidelines: Array<DrugWithGuidelines>,
): Array<DrugWithGuidelines> {
return drugsWithGuidelines.map(({ drug, guidelines }) => {
guidelines.forEach((guideline) => {
const informationMap = new Map<string, IExternalData>();
guideline.externalData.forEach((externalData) => {
const key = informationKey(externalData);
if (!informationMap.has(key)) {
informationMap.set(key, externalData);
}
});
guideline.externalData = Array.from(informationMap.values());
});
return { drug, guidelines };
});
}

export interface DrugWithGuidelines {
drug: IDrug_Any;
guidelines: Array<IGuideline_Any>;
Expand All @@ -86,56 +144,21 @@ export function getDrugsWithContractedGuidelines(
recommendations: Array<CpicRecommendation>,
source: string,
): Array<DrugWithGuidelines> {
const guidelineKeyMap = new Map<string, IGuideline_Any>();
const drugIdMap = new Map<string, DrugWithGuidelines>();

// merge same-information guidelines
function processContractedGuideline(
rec: CpicRecommendation,
): IGuideline_Any | null {
const key = guidelineKey(rec);
if (guidelineKeyMap.has(key)) {
const existingGuideline = guidelineKeyMap.get(key);
Object.keys(existingGuideline!.lookupkey).forEach((gene) => {
existingGuideline!.lookupkey[gene].push(rec.lookupkey[gene]);
});
Object.keys(existingGuideline!.phenotypes).forEach((gene) => {
existingGuideline!.phenotypes[gene].push(rec.phenotypes[gene]);
});
return null;
}
const guideline = guidelineFromRecommendation(rec, source);
guidelineKeyMap.set(key, guideline);
return guideline;
}

recommendations.forEach((rec) => {
const newGuideline = processContractedGuideline(rec);
const newGuideline = guidelineFromRecommendation(rec, source);
const existing = drugIdMap.get(rec.drugid);
if (existing && newGuideline) {
if (existing) {
existing.guidelines.push(newGuideline);
}
if (!existing) {
} else {
drugIdMap.set(rec.drugid, {
drug: drugFromRecommendation(rec),
guidelines: newGuideline ? [newGuideline] : [],
guidelines: [newGuideline],
});
}
});

// merge same-phenotype guidelines
return Array.from(drugIdMap.values()).map(({ drug, guidelines }) => {
const phenotypeMap = new Map<string, IGuideline_Any>();
guidelines.forEach((guideline) => {
const key = phenotypeKey(guideline);
if (phenotypeMap.has(key)) {
phenotypeMap
.get(key)!
.externalData.push(guideline.externalData[0]);
} else {
phenotypeMap.set(key, guideline);
}
});
return { drug, guidelines: Array.from(phenotypeMap.values()) };
});
return contractByInformation(
contractByPhenotype(Array.from(drugIdMap.values())),
);
}
21 changes: 11 additions & 10 deletions anni/src/database/models/Guideline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ import { BrickResolver, resolveStringOrFail } from '../helpers/resolve-bricks';
import { makeIdsStrings, OptionalId } from '../helpers/types';
import { versionedModel } from '../versioning/schema';

export type IExternalData = {
source: string;
recommendationId?: number;
recommendationVersion?: number;
guidelineName: string;
guidelineUrl: string;
implications: { [key: string]: string }; // gene-symbol: implication
recommendation: string;
comments?: string;
};
export interface IGuideline<
AnnotationT extends BrickAnnotationT,
IdT extends OptionalId = undefined,
Expand All @@ -30,16 +40,7 @@ export interface IGuideline<
> {
lookupkey: { [key: string]: [string] }; // gene-symbol: phenotype-description
phenotypes: { [key: string]: [string] }; // gene-symbol: phenotype
externalData: Array<{
source: string;
recommendationId?: number;
recommendationVersion?: number;
guidelineName: string;
guidelineUrl: string;
implications: { [key: string]: string }; // gene-symbol: implication
recommendation: string;
comments?: string;
}>;
externalData: Array<IExternalData>;
}
export type IGuideline_DB = IGuideline<Types.ObjectId[], Types.ObjectId> & {
curationState: CurationState;
Expand Down

0 comments on commit f8a1a90

Please sign in to comment.