Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…raph_handler into retriever-backport
  • Loading branch information
rjawesome committed Aug 12, 2024
2 parents 06e2cc5 + df25494 commit 0d00d2e
Show file tree
Hide file tree
Showing 8 changed files with 301 additions and 186 deletions.
2 changes: 2 additions & 0 deletions data/templateGroups.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
],
"predicate": ["affects"],
"qualifiers": {
"qualified_predicate": "causes",
"object_aspect_qualifier": "activity_or_abundance",
"object_direction_qualifier": "increased"
},
Expand All @@ -40,6 +41,7 @@
],
"predicate": ["affects"],
"qualifiers": {
"qualified_predicate": "causes",
"object_aspect_qualifier": "activity_or_abundance",
"object_direction_qualifier": "decreased"
},
Expand Down
7 changes: 2 additions & 5 deletions src/edge_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,11 +208,8 @@ export default class QueryEdgeManager {
const objectIDs = [record.object.original, record.object.curie, ...record.object.equivalentCuries];

// there must be at least a minimal intersection
const subjectMatch =
subjectIDs.some((curie) => execSubjectCuries.includes(curie)) || execSubjectCuries.length === 0;
const objectMatch = objectIDs.some((curie) => execObjectCuries.includes(curie)) || execObjectCuries.length === 0;

//if both ends match then keep record
const subjectMatch = subjectIDs.some((curie) => execSubjectCuries.includes(curie));
const objectMatch = objectIDs.some((curie) => execObjectCuries.includes(curie));

// Don't keep self-edges
const selfEdge = [...subjectIDs].some((curie) => objectIDs.includes(curie));
Expand Down
19 changes: 19 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
} from '@biothings-explorer/types';
import { QueryHandlerOptions, QEdge } from '@biothings-explorer/types';
import { Telemetry } from '@biothings-explorer/utils';
import { enrichTrapiResultsWithPfocrFigures } from './results_assembly/pfocr';

// Exports for external availability
export * from './types';
Expand Down Expand Up @@ -308,6 +309,18 @@ export default class TRAPIQueryHandler {
this.finalizedResults = fixedResults;
}

appendOriginalCuriesToResults(results: TrapiResult[]): void {
results.forEach(result => {
Object.entries(result.node_bindings).forEach(([_, bindings]) => {
bindings.forEach(binding => {
if (this.bteGraph.nodes[binding.id].originalCurie && this.bteGraph.nodes[binding.id].originalCurie !== binding.id) {
binding.query_id = this.bteGraph.nodes[binding.id].originalCurie;
}
})
})
})
}

async addQueryNodes(): Promise<void> {
const qNodeIDsByOriginalID: Map<string, TrapiQNode> = new Map();
const curiesToResolve = [
Expand All @@ -329,6 +342,7 @@ export default class TRAPIQueryHandler {
this.bteGraph.nodes[resolvedEntity.primaryID] = new KGNode(resolvedEntity.primaryID, {
primaryCurie: resolvedEntity.primaryID,
qNodeID: qNodeIDsByOriginalID[originalCurie],
originalCurie: originalCurie,
curies: resolvedEntity.equivalentIDs,
names: resolvedEntity.labelAliases,
semanticType: category ? [category] : ['biolink:NamedThing'],
Expand Down Expand Up @@ -692,8 +706,13 @@ export default class TRAPIQueryHandler {
this.createSubclassSupportGraphs();
// prune bteGraph
this.bteGraph.prune(this.finalizedResults, this.auxGraphs);
// add original curies to results
this.appendOriginalCuriesToResults(this.finalizedResults);
this.bteGraph.notify();

// Attempt to enrich results with PFOCR figures
this.logs = [...this.logs, ...(await enrichTrapiResultsWithPfocrFigures(this.getResponse()))];

span3?.finish();

// check primary knowledge sources
Expand Down
91 changes: 69 additions & 22 deletions src/inferred_mode/inferred_mode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
TrapiKnowledgeGraph,
TrapiQEdge,
TrapiQNode,
TrapiQualifier,
TrapiQueryGraph,
TrapiResponse,
TrapiResult,
Expand Down Expand Up @@ -168,10 +169,8 @@ export default class InferredQueryHandler {
const qualifierConstraints = (qEdge.qualifier_constraints || []).map((qualifierSetObj) => {
return Object.fromEntries(
qualifierSetObj.qualifier_set.map(({ qualifier_type_id, qualifier_value }) => [
qualifier_type_id.replace('biolink:', ''),
Array.isArray(qualifier_value)
? qualifier_value.map((string) => string.replace('biolink:', ''))
: qualifier_value.replace('biolink:', ''),
qualifier_type_id,
qualifier_value,
]),
) as CompactQualifiers;
});
Expand Down Expand Up @@ -214,7 +213,7 @@ export default class InferredQueryHandler {
async createQueries(qEdge: TrapiQEdge, qSubject: TrapiQNode, qObject: TrapiQNode): Promise<FilledTemplate[]> {
const templates = await this.findTemplates(qEdge, qSubject, qObject);
// combine creative query with templates
const subQueries = templates.map(({ template, queryGraph }) => {
const subQueries = templates.map(({ template, queryGraph, qualifiers }) => {
queryGraph.nodes.creativeQuerySubject.categories = [
...new Set([...queryGraph.nodes.creativeQuerySubject.categories, ...qSubject.categories]),
];
Expand Down Expand Up @@ -244,7 +243,7 @@ export default class InferredQueryHandler {
delete queryGraph.nodes.creativeQueryObject.ids;
}

return { template, queryGraph };
return { template, queryGraph, qualifiers };
});

return subQueries;
Expand All @@ -256,6 +255,7 @@ export default class InferredQueryHandler {
qEdgeID: string,
qEdge: TrapiQEdge,
combinedResponse: CombinedResponse,
qualifiers?: CompactQualifiers,
): CombinedResponseReport {
const span = Telemetry.startSpan({ description: 'creativeCombineResponse' });
const newResponse = handler.getResponse();
Expand Down Expand Up @@ -309,8 +309,43 @@ export default class InferredQueryHandler {
const resultID = `${resultCreativeSubjectID}-${resultCreativeObjectID}`;

// Direct edge answers stand on their own, not as an inferred edge.
if (Object.keys(result.node_bindings).length == 2) {
const boundEdgeID = Object.values(result.analyses[0].edge_bindings)[0][0].id;
const boundEdgeID = Object.values(result.analyses[0].edge_bindings)[0][0].id;
const boundEdge = combinedResponse.message.knowledge_graph.edges[boundEdgeID];
const oneHop = Object.keys(result.node_bindings).length === 2; // Direct edge
// Predicate matches or is descendant
const predicateMatch =
qEdge.predicates?.some(
(predicate) =>
predicate === boundEdge.predicate ||
biolink.getDescendantPredicates(predicate).includes(boundEdge.predicate),
) ?? false;
// All query qualifiers (if any) are accounted for (more is fine)
const qualifierMatch =
!qEdge.qualifier_constraints ||
qEdge.qualifier_constraints.length === 0 ||
qEdge.qualifier_constraints?.some(({ qualifier_set }) => {
return qualifier_set.every((queryQualifier) => {
return (
boundEdge.qualifiers?.some((qualifier) => {
const typeMatch = queryQualifier.qualifier_type_id === qualifier.qualifier_type_id;
let valueMatch: boolean;
try {
const descendants = queryQualifier.qualifier_value.includes('biolink:')
? biolink.getDescendantPredicates(queryQualifier.qualifier_value as string)
: biolink.getDescendantQualifiers(queryQualifier.qualifier_value as string);
valueMatch =
queryQualifier.qualifier_value === qualifier.qualifier_value ||
descendants.includes(qualifier.qualifier_value as string);
} catch (err) {
valueMatch = queryQualifier.qualifier_value === qualifier.qualifier_value;
}
return typeMatch && valueMatch;
}) ?? false
);
});
});
const specialHandling = oneHop && predicateMatch && qualifierMatch;
if (specialHandling) {
translatedResult.analyses[0].edge_bindings = { [qEdgeID]: [{ id: boundEdgeID, attributes: [] }] };
} else {
// Create an aux graph using the result and associate it with an inferred Edge
Expand All @@ -334,11 +369,25 @@ export default class InferredQueryHandler {
],
attributes: [
{ attribute_type_id: 'biolink:support_graphs', value: [] },
{ attribute_type_id: 'biolink:knowledge_level', value: "prediction" },
{ attribute_type_id: 'biolink:agent_type', value: "computational_model" },
{ attribute_type_id: 'biolink:knowledge_level', value: 'prediction' },
{ attribute_type_id: 'biolink:agent_type', value: 'computational_model' },
],
};
}
// Add qualifiers to edge
if (
typeof qualifiers == 'object' &&
Object.keys(qualifiers).length > 0 &&
!combinedResponse.message.knowledge_graph.edges[inferredEdgeID].qualifiers
) {
combinedResponse.message.knowledge_graph.edges[inferredEdgeID].qualifiers = Object.entries(qualifiers).map(
([qualifierType, qualifierValue]) => ({
qualifier_type_id: qualifierType,
qualifier_value: qualifierValue,
}),
);
}

let auxGraphSuffix = 0;
while (
Object.keys(combinedResponse.message.auxiliary_graphs).includes(`${inferredEdgeID}-support${auxGraphSuffix}`)
Expand All @@ -357,7 +406,7 @@ export default class InferredQueryHandler {
},
[] as string[],
),
attributes: []
attributes: [],
};
}

Expand Down Expand Up @@ -390,9 +439,9 @@ export default class InferredQueryHandler {
if (typeof combinedResponse.message.results[resultID].analyses[0].score !== 'undefined') {
combinedResponse.message.results[resultID].analyses[0].score = resScore
? scaled_sigmoid(
inverse_scaled_sigmoid(combinedResponse.message.results[resultID].analyses[0].score) +
inverse_scaled_sigmoid(resScore),
)
inverse_scaled_sigmoid(combinedResponse.message.results[resultID].analyses[0].score) +
inverse_scaled_sigmoid(resScore),
)
: combinedResponse.message.results[resultID].analyses[0].score;
} else {
combinedResponse.message.results[resultID].analyses[0].score = resScore;
Expand Down Expand Up @@ -523,7 +572,7 @@ export default class InferredQueryHandler {
[resultID: string]: number;
} = {};

await async.eachOfSeries(subQueries, async ({ template, queryGraph }, i) => {
await async.eachOfSeries(subQueries, async ({ template, queryGraph, qualifiers }, i) => {
const span = Telemetry.startSpan({ description: 'creativeTemplate' });
span.setData('template', (i as number) + 1);
i = i as number;
Expand All @@ -546,6 +595,7 @@ export default class InferredQueryHandler {
qEdgeID,
qEdge,
combinedResponse,
qualifiers,
);
// update values used in logging
successfulQueries += querySuccess;
Expand All @@ -560,11 +610,9 @@ export default class InferredQueryHandler {
const message = [
`Addition of ${creativeLimitHit} results from Template ${i + 1}`,
Object.keys(combinedResponse.message.results).length === this.CREATIVE_LIMIT ? ' meets ' : ' exceeds ',
`creative result maximum of ${this.CREATIVE_LIMIT} (reaching ${
Object.keys(combinedResponse.message.results).length
`creative result maximum of ${this.CREATIVE_LIMIT} (reaching ${Object.keys(combinedResponse.message.results).length
} merged). `,
`Response will be truncated to top-scoring ${this.CREATIVE_LIMIT} results. Skipping remaining ${
subQueries.length - (i + 1)
`Response will be truncated to top-scoring ${this.CREATIVE_LIMIT} results. Skipping remaining ${subQueries.length - (i + 1)
} `,
subQueries.length - (i + 1) === 1 ? `template.` : `templates.`,
].join('');
Expand All @@ -589,9 +637,8 @@ export default class InferredQueryHandler {
const total =
Object.values(mergedResultsCount).reduce((sum, count) => sum + count, 0) +
Object.keys(mergedResultsCount).length;
const message = `Merging Summary: (${total}) inferred-template results were merged into (${
Object.keys(mergedResultsCount).length
}) final results, reducing result count by (${total - Object.keys(mergedResultsCount).length})`;
const message = `Merging Summary: (${total}) inferred-template results were merged into (${Object.keys(mergedResultsCount).length
}) final results, reducing result count by (${total - Object.keys(mergedResultsCount).length})`;
debug(message);
combinedResponse.logs.push(new LogEntry('INFO', null, message).getLog());
}
Expand Down
36 changes: 23 additions & 13 deletions src/inferred_mode/template_lookup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,13 @@ export interface TemplateLookup {
subject: string;
object: string;
predicate: string;
qualifiers: {
[qualifierType: string]: string;
};
qualifiers: CompactQualifiers;
}

export interface MatchedTemplate {
template: string;
queryGraph: TrapiQueryGraph;
qualifiers: CompactQualifiers;
}

export interface TemplateGroup {
Expand All @@ -35,6 +34,11 @@ export interface CompactEdge {
qualifiers: CompactQualifiers;
}

interface PathMatch {
path: string;
qualifiers: CompactQualifiers;
}

export async function getTemplates(lookups: TemplateLookup[]): Promise<MatchedTemplate[]> {
async function getFiles(dir: string): Promise<string[]> {
const rootFiles = await fs.readdir(path.resolve(dir));
Expand All @@ -55,31 +59,37 @@ export async function getTemplates(lookups: TemplateLookup[]): Promise<MatchedTe
const templateGroups = JSON.parse(
await fs.readFile(path.resolve(__dirname, '../../data/templateGroups.json'), { encoding: 'utf8' }),
);
const matchingTemplatePaths: string[] = templateGroups.reduce((matches: string[], group: TemplateGroup) => {
const matchingTemplatePaths: PathMatch[] = templateGroups.reduce((matches: PathMatch[], group: TemplateGroup) => {
let matchingQualifers: CompactQualifiers;
const lookupMatch = lookups.some((lookup) => {
return (
const match =
group.subject.includes(lookup.subject) &&
group.object.includes(lookup.object) &&
group.predicate.includes(lookup.predicate) &&
Object.entries(lookup.qualifiers || {}).every(([qualifierType, qualifierValue]) => {
return (group.qualifiers || {})[qualifierType] && group.qualifiers[qualifierType] === qualifierValue;
})
);
return (
(group.qualifiers || {})[qualifierType.replace('biolink:', '')] &&
group.qualifiers[qualifierType.replace('biolink:', '')] === qualifierValue.replace('biolink:', '')
);
});
if (match) matchingQualifers = lookup.qualifiers;
return match;
});

if (lookupMatch) {
group.templates.forEach((template) => {
if (!matches.includes(templatePaths[template])) {
matches.push(templatePaths[template]);
if (!matches.find((t) => t.path === templatePaths[template])) {
matches.push({ path: templatePaths[template], qualifiers: matchingQualifers });
}
});
}
return matches;
}, [] as string[]);
return await async.map(matchingTemplatePaths, async (templatePath: string) => {
return await async.map(matchingTemplatePaths, async (templatePathObj: PathMatch) => {
return {
template: templatePath.substring(templatePath.lastIndexOf('/') + 1),
queryGraph: JSON.parse(await fs.readFile(templatePath, { encoding: 'utf8' })).message.query_graph,
template: templatePathObj.path.substring(templatePathObj.path.lastIndexOf('/') + 1),
queryGraph: JSON.parse(await fs.readFile(templatePathObj.path, { encoding: 'utf8' })).message.query_graph,
qualifiers: templatePathObj.qualifiers,
};
});
}
Expand Down
Loading

0 comments on commit 0d00d2e

Please sign in to comment.