Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Smartapi test #195

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion __test__/integration/QueryEdge.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import QNode from '../../src/query_node';
import QEdge from '../../src/query_edge';
import KGNode from '../../src/graph/kg_node';
import KGEdge from '../../src/graph/kg_edge';
import KnowledgeGraph from '../../src/graph/knowledge_graph';

describe('Testing QueryEdge Module', () => {
const gene_node1 = new QNode({ id: 'n1', categories: ['Gene'], ids: ['NCBIGene:1017'] });
Expand Down Expand Up @@ -143,7 +146,7 @@ describe('Testing QueryEdge Module', () => {
expect(res).toContain('contributes_to');
expect(res).toContain('causes');
expect(res).toContain('ameliorates');
expect(res).toContain('treats');
// expect(res).toContain('treats');
});

test('Predicates not in biolink model should return itself', () => {
Expand Down Expand Up @@ -198,4 +201,20 @@ describe('Testing QueryEdge Module', () => {
// NOTE: recently changed from not.toEqual, because an unfrozen edge *should* equal its original?
expect(qEdge1.getHashedEdgeRepresentation()).toEqual(qEdge2.getHashedEdgeRepresentation());
});

test('meetsConstraints', () => {
const qEdge = new QEdge({ id: 'e01', subject: type_node, object: disease1_node, predicates: ['biolink:treats'], attribute_constraints: [{ name: 'publications', id: 'biolink:publications', operator: '==', value: 'PMID:9248614', not: false }] });
const kgNode1 = new KGNode("node1", { label: "node1", semanticType: [], names: [], curies: [], primaryCurie: "node1", qNodeID: "e01"});
const kgNode2 = new KGNode("node2", { label: "node2", semanticType: [], names: [], curies: [], primaryCurie: "node2", qNodeID: "e01"});
const kgEdge1 = new KGEdge("edge1", {object: "node1", subject: "node2", predicate: "biolink:treats"});
const kgEdge2 = new KGEdge("edge2", {object: "node1", subject: "node2", predicate: "biolink:treats"});
kgEdge1.addPublication("PMID:9248614");
kgEdge1.addPublication("PMID:1234567");
kgEdge2.addPublication("PMID:7614243");
kgEdge2.addPublication("PMID:1234567");
const graph = new KnowledgeGraph();
graph.update({ nodes: { node1: kgNode1, node2: kgNode2 }, edges: { edge1: kgEdge1, edge2: kgEdge2 } });
expect(qEdge.meetsConstraints(graph.edges["edge1"], graph.nodes["node1"], graph.nodes["node2"])).toBeTruthy();
expect(qEdge.meetsConstraints(graph.edges["edge2"], graph.nodes["node1"], graph.nodes["node2"])).toBeFalsy();
})
});
36 changes: 33 additions & 3 deletions src/edge_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { UnavailableAPITracker } from './types';
import { RecordsByQEdgeID } from './results_assembly/query_results';
import path from 'path';
import { promises as fs } from 'fs';
import KnowledgeGraph from './graph/knowledge_graph';
import BTEGraph from './graph/graph';

export default class QueryEdgeManager {
private _qEdges: QEdge[];
Expand Down Expand Up @@ -211,7 +213,7 @@ export default class QueryEdgeManager {
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
// if both ends match then keep record

// Don't keep self-edges
const selfEdge = [...subjectIDs].some((curie) => objectIDs.includes(curie));
Expand All @@ -230,6 +232,32 @@ export default class QueryEdgeManager {
return keep;
}

_constrainEdgeRecords(qEdge: QEdge, records: Record[]) {
const keep: Record[] = [];
const bte = new BTEGraph();
const kg = new KnowledgeGraph();
bte.update(records);
kg.update(bte);
records.forEach(record => {
const edge = kg.kg.edges[record.recordHash];
const sub = qEdge.reverse ? kg.kg.nodes[edge.object] : kg.kg.nodes[edge.subject];
const obj = qEdge.reverse ? kg.kg.nodes[edge.subject] : kg.kg.nodes[edge.object];
if (qEdge.meetsConstraints(edge, sub, obj)) {
keep.push(record);
}
});

debug(`'${qEdge.getID()}' dropped (${records.length - keep.length}) records based on edge/node constraints.`);
this.logs.push(
new LogEntry(
'DEBUG',
null,
`'${qEdge.getID()}' kept (${keep.length}) / dropped (${records.length - keep.length}) records (based on node/edge constraints).`,
).getLog(),
);
return keep;
}

collectRecords(): boolean {
//go through edges and collect records organized by edge
let recordsByQEdgeID: RecordsByQEdgeID = {};
Expand Down Expand Up @@ -296,8 +324,10 @@ export default class QueryEdgeManager {

updateEdgeRecords(currentQEdge: QEdge): void {
//1. filter edge records based on current status
const filteredRecords = this._filterEdgeRecords(currentQEdge);
//2.trigger node update / entity update based on new status
let filteredRecords = this._filterEdgeRecords(currentQEdge);
//2. make sure node/edge constraints are met
filteredRecords = this._constrainEdgeRecords(currentQEdge, filteredRecords);
//3. trigger node update / entity update based on new status
currentQEdge.storeRecords(filteredRecords);
}

Expand Down
3 changes: 3 additions & 0 deletions src/graph/knowledge_graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
TrapiKGNodes,
TrapiQualifier,
TrapiSource,
TrapiAttributeConstraint,
} from '@biothings-explorer/types';
import KGNode from './kg_node';
import KGEdge from './kg_edge';
Expand Down Expand Up @@ -162,6 +163,8 @@ export default class KnowledgeGraph {
}

update(bteGraph: BTEGraphUpdate): void {
this.nodes = {};
this.edges = {};
Object.keys(bteGraph.nodes).map((node) => {
this.nodes[bteGraph.nodes[node].primaryCurie] = this._createNode(bteGraph.nodes[node]);
});
Expand Down
53 changes: 42 additions & 11 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export { getTemplates, supportedLookups } from './inferred_mode/template_lookup'
export { default as QEdge } from './query_edge';
export { default as QNode } from './query_node';
export { default as InvalidQueryGraphError } from './exceptions/invalid_query_graph_error';
export { default as QEdge2APIEdgeHandler } from './qedge2apiedge'
export * from './qedge2apiedge';

export default class TRAPIQueryHandler {
Expand All @@ -52,6 +53,7 @@ export default class TRAPIQueryHandler {
auxGraphs: TrapiAuxGraphCollection;
finalizedResults: TrapiResult[];
queryGraph: TrapiQueryGraph;
queryGraphHandler: QueryGraph;
constructor(
options: QueryHandlerOptions = {},
smartAPIPath: string = undefined,
Expand Down Expand Up @@ -209,6 +211,7 @@ export default class TRAPIQueryHandler {
let auxGraphs: { [supportGraphID: string]: TrapiAuxiliaryGraph } = {};
const edgesToRebind = {};
const edgesIDsByAuxGraphID = {};
const supportGraphsByEdgeID = {};
Object.entries(this.bteGraph.edges).forEach(([edgeID, bteEdge]) => {
if (edgeID.includes('expanded')) return;
const supportGraph = [edgeID];
Expand All @@ -227,6 +230,7 @@ export default class TRAPIQueryHandler {
suffix += 1;
}
const supportGraphID = `support${suffix}-${boundEdgeID}`;
supportGraphsByEdgeID[edgeID] = supportGraphID;
auxGraphs[supportGraphID] = { edges: supportGraph, attributes: [] };
if (!edgesIDsByAuxGraphID[supportGraphID]) {
edgesIDsByAuxGraphID[supportGraphID] = new Set();
Expand Down Expand Up @@ -291,6 +295,32 @@ export default class TRAPIQueryHandler {
newBindings.push(binding);
boundIDs.add(binding.id);
}
} else if (this.queryGraph.edges[qEdgeID].attribute_constraints) {
const oldBoundEdge = this.bteGraph.edges[edgesToRebind[binding.id]];
const newBoundEdge = `${edgesToRebind[binding.id]}-constrained_by_${qEdgeID}`;
if (!boundIDs.has(newBoundEdge)) {
this.bteGraph.edges[newBoundEdge] = new KGEdge(newBoundEdge, {
predicate: oldBoundEdge.predicate,
subject: oldBoundEdge.subject,
object: oldBoundEdge.object,
});
this.bteGraph.edges[newBoundEdge].addAdditionalAttributes('biolink:support_graphs', []);
this.bteGraph.edges[newBoundEdge].addAdditionalAttributes('biolink:knowledge_level', 'logical_entailment')
this.bteGraph.edges[newBoundEdge].addAdditionalAttributes('biolink:agent_type', 'automated_agent')
this.bteGraph.edges[newBoundEdge].addSource([
{
resource_id: this.options.provenanceUsesServiceProvider
? 'infores:service-provider-trapi'
: 'infores:biothings-explorer',
resource_role: 'primary_knowledge_source',
},
]);
boundIDs.add(newBoundEdge);
newBindings.push({...binding, id: newBoundEdge });
resultBoundEdgesWithAuxGraphs.add(newBoundEdge);
}
(this.bteGraph.edges[newBoundEdge].attributes['biolink:support_graphs'] as Set<string>).add(supportGraphsByEdgeID[binding.id]);
edgesIDsByAuxGraphID[supportGraphsByEdgeID[binding.id]].add(newBoundEdge);
} else if (!boundIDs.has(edgesToRebind[binding.id])) {
newBindings.push({ id: edgesToRebind[binding.id], attributes: [] });
boundIDs.add(edgesToRebind[binding.id]);
Expand Down Expand Up @@ -407,6 +437,7 @@ export default class TRAPIQueryHandler {
}
}
}
this.queryGraphHandler = new QueryGraph(queryGraph, this.options.schema);
}

_initializeResponse(): void {
Expand All @@ -416,11 +447,10 @@ export default class TRAPIQueryHandler {
this.bteGraph.subscribe(this.knowledgeGraph);
}

async _processQueryGraph(queryGraph: TrapiQueryGraph): Promise<QEdge[]> {
async _processQueryGraph(): Promise<QEdge[]> {
try {
const queryGraphHandler = new QueryGraph(queryGraph, this.options.schema);
const queryEdges = await queryGraphHandler.calculateEdges();
this.logs = [...this.logs, ...queryGraphHandler.logs];
const queryEdges = await this.queryGraphHandler.calculateEdges();
this.logs = [...this.logs, ...this.queryGraphHandler.logs];
return queryEdges;
} catch (err) {
if (err instanceof InvalidQueryGraphError || err instanceof SRINodeNormFailure) {
Expand Down Expand Up @@ -528,6 +558,9 @@ export default class TRAPIQueryHandler {
this.logs.push(new LogEntry('WARNING', null, message).getLog());
return;
}
if (await this._checkContraints()) {
return;
}
const inferredQueryHandler = new InferredQueryHandler(
this,
this.queryGraph,
Expand Down Expand Up @@ -564,7 +597,7 @@ export default class TRAPIQueryHandler {
new LogEntry(
'ERROR',
null,
`BTE does not currently support any type of constraint. Your query Terminates.`,
`BTE does not currently support constraints with creative mode. Your query Terminates.`,
).getLog(),
);
return true;
Expand Down Expand Up @@ -656,11 +689,8 @@ export default class TRAPIQueryHandler {
);
}

const queryEdges = await this._processQueryGraph(this.queryGraph);
// TODO remove this when constraints implemented
if (await this._checkContraints()) {
return;
}
const queryEdges = await this._processQueryGraph();

if ((this.options.smartAPIID || this.options.teamName) && Object.values(this.queryGraph.edges).length > 1) {
const message = 'smartAPI/team-specific endpoints only support single-edge queries. Your query terminates.';
this.logs.push(new LogEntry('WARNING', null, message).getLog());
Expand Down Expand Up @@ -691,10 +721,11 @@ export default class TRAPIQueryHandler {

// update query graph
this.bteGraph.update(manager.getRecords());
this.bteGraph.notify();
//update query results
await this.trapiResultsAssembler.update(
manager.getOrganizedRecords(),
!(this.options.smartAPIID || this.options.teamName),
!(this.options.smartAPIID || this.options.teamName)
);
this.logs = [...this.logs, ...this.trapiResultsAssembler.logs];
// fix subclassing
Expand Down
Loading
Loading