From 05d7b4635ea39e832d64192a21fde82a20580ed8 Mon Sep 17 00:00:00 2001 From: tokebe <43009413+tokebe@users.noreply.github.com> Date: Wed, 21 Aug 2024 14:59:37 -0400 Subject: [PATCH] fix: use sources passed from node expansion --- src/edge_manager.ts | 48 +++++++++++--------- src/index.ts | 107 ++++++++++++++++++++++++++------------------ src/types.ts | 11 +++-- 3 files changed, 98 insertions(+), 68 deletions(-) diff --git a/src/edge_manager.ts b/src/edge_manager.ts index 92a7a74b..79f00dcf 100644 --- a/src/edge_manager.ts +++ b/src/edge_manager.ts @@ -110,7 +110,7 @@ export default class QueryEdgeManager { } debug( `(5) Sending next edge '${nextQEdge.getID()}' ` + - `WITH entity count...(${nextQEdge.subject.entity_count || nextQEdge.object.entity_count})`, + `WITH entity count...(${nextQEdge.subject.entity_count || nextQEdge.object.entity_count})`, ); return this.preSendOffCheck(nextQEdge); } @@ -119,9 +119,9 @@ export default class QueryEdgeManager { this._qEdges.forEach((qEdge) => { debug( `'${qEdge.getID()}'` + - ` : (${qEdge.subject.entity_count || 0}) ` + - `${qEdge.reverse ? '<--' : '-->'}` + - ` (${qEdge.object.entity_count || 0})`, + ` : (${qEdge.subject.entity_count || 0}) ` + + `${qEdge.reverse ? '<--' : '-->'}` + + ` (${qEdge.object.entity_count || 0})`, ); }); } @@ -129,8 +129,9 @@ export default class QueryEdgeManager { _logSkippedQueries(unavailableAPIs: UnavailableAPITracker): void { Object.entries(unavailableAPIs).forEach(([api, { skippedQueries }]) => { if (skippedQueries > 0) { - const skipMessage = `${skippedQueries} additional quer${skippedQueries > 1 ? 'ies' : 'y'} to ${api} ${skippedQueries > 1 ? 'were' : 'was' - } skipped as the API was unavailable.`; + const skipMessage = `${skippedQueries} additional quer${skippedQueries > 1 ? 'ies' : 'y'} to ${api} ${ + skippedQueries > 1 ? 'were' : 'was' + } skipped as the API was unavailable.`; debug(skipMessage); this.logs.push(new LogEntry('WARNING', null, skipMessage).getLog()); } @@ -196,7 +197,7 @@ export default class QueryEdgeManager { const objectCuries = qEdge.object.curie; debug( `'${qEdge.getID()}' Reversed[${qEdge.reverse}] (${JSON.stringify(subjectCuries.length || 0)})` + - `--(${JSON.stringify(objectCuries.length || 0)}) entities / (${records.length}) records.`, + `--(${JSON.stringify(objectCuries.length || 0)}) entities / (${records.length}) records.`, ); // debug(`IDS SUB ${JSON.stringify(sub_count)}`) // debug(`IDS OBJ ${JSON.stringify(obj_count)}`) @@ -209,18 +210,24 @@ export default class QueryEdgeManager { let objectIDs = [record.object.original, record.object.curie, ...record.object.equivalentCuries]; // check if IDs will be resolved to a parent - subjectIDs = [...subjectIDs, ...subjectIDs.reduce((set, subjectID) => { - Object.entries(this._subclassEdges[subjectID] ?? {}).forEach(([id, qNodes]) => { - if (qNodes.includes(qEdge.reverse ? qEdge.object.id : qEdge.subject.id)) set.add(id); - }); - return set; - }, new Set())]; - objectIDs = [...objectIDs, ...objectIDs.reduce((set, objectID) => { - Object.entries(this._subclassEdges[objectID] ?? {}).forEach(([id, qNodes]) => { - if (qNodes.includes(qEdge.reverse ? qEdge.subject.id : qEdge.object.id)) set.add(id); - }); - return set; - }, new Set())]; + subjectIDs = [ + ...subjectIDs, + ...subjectIDs.reduce((set, subjectID) => { + Object.entries(this._subclassEdges[subjectID] ?? {}).forEach(([id, { qNodes }]) => { + if (qNodes.includes(qEdge.reverse ? qEdge.object.id : qEdge.subject.id)) set.add(id); + }); + return set; + }, new Set()), + ]; + objectIDs = [ + ...objectIDs, + ...objectIDs.reduce((set, objectID) => { + Object.entries(this._subclassEdges[objectID] ?? {}).forEach(([id, { qNodes }]) => { + if (qNodes.includes(qEdge.reverse ? qEdge.subject.id : qEdge.object.id)) set.add(id); + }); + return set; + }, new Set()), + ]; // there must be at least a minimal intersection const subjectMatch = subjectIDs.some((curie) => execSubjectCuries.includes(curie)); @@ -409,7 +416,8 @@ export default class QueryEdgeManager { new LogEntry( 'INFO', null, - `Executing ${currentQEdge.getID()}${currentQEdge.isReversed() ? ' (reversed)' : ''}: ${currentQEdge.subject.id + `Executing ${currentQEdge.getID()}${currentQEdge.isReversed() ? ' (reversed)' : ''}: ${ + currentQEdge.subject.id } ${currentQEdge.isReversed() ? '<--' : '-->'} ${currentQEdge.object.id}`, ).getLog(), ); diff --git a/src/index.ts b/src/index.ts index 4b3f11f6..71cf9e50 100644 --- a/src/index.ts +++ b/src/index.ts @@ -79,7 +79,7 @@ export default class TRAPIQueryHandler { if (this.options.smartapi) { smartapiRegistry = this.options.smartapi; } else { - const file = await fs.readFile(this.path, "utf-8"); + const file = await fs.readFile(this.path, 'utf-8'); smartapiRegistry = JSON.parse(file); } @@ -109,8 +109,8 @@ export default class TRAPIQueryHandler { `Query options are: ${JSON.stringify({ ...this.options, schema: this.options.schema ? this.options.schema.info.version : 'not included', - metakg: "", - smartapi: "" + metakg: '', + smartapi: '', })}`, ); @@ -172,9 +172,13 @@ export default class TRAPIQueryHandler { const nodesToRebind: { [nodeID: string]: { [qEdgeID: string]: { newNode: string; subclassEdgeID: string } } } = {}; Object.keys(this.bteGraph.nodes).forEach((nodeID) => { const subclassCuries = []; - expandedIDsbyPrimaryID[nodeID]?.forEach((expandedID) => Object.keys(this.subclassEdges[expandedID]).forEach((parentID) => subclassCuries.push({ original: parentID, expanded: expandedID }))); + expandedIDsbyPrimaryID[nodeID]?.forEach((expandedID) => + Object.keys(this.subclassEdges[expandedID]).forEach((parentID) => + subclassCuries.push({ original: parentID, expanded: expandedID }), + ), + ); if (!subclassCuries.length) return; // Nothing to rebind - subclassCuries.forEach(({original, expanded}) => { + subclassCuries.forEach(({ original, expanded }) => { const subject = nodeID; const object = primaryIDsByOriginalID[original]; // Don't keep self-subclass @@ -186,11 +190,9 @@ export default class TRAPIQueryHandler { subject, object, }); - const source = Object.entries(ontologyKnowledgeSourceMapping).find(([prefix]) => { - return expanded.includes(prefix); - })[1]; - subclassEdge.addAdditionalAttributes('biolink:knowledge_level', 'knowledge_assertion') - subclassEdge.addAdditionalAttributes('biolink:agent_type', 'manual_agent') + const source = ontologyKnowledgeSourceMapping[this.subclassEdges[expanded][original].source] ?? 'error-not-provided'; + subclassEdge.addAdditionalAttributes('biolink:knowledge_level', 'knowledge_assertion'); + subclassEdge.addAdditionalAttributes('biolink:agent_type', 'manual_agent'); subclassEdge.addSource([ { resource_id: source, resource_role: 'primary_knowledge_source' }, { @@ -202,39 +204,47 @@ export default class TRAPIQueryHandler { ]); this.bteGraph.edges[subclassEdgeID] = subclassEdge; if (!nodesToRebind[subject]) nodesToRebind[subject] = {}; - this.subclassEdges[expanded][original].forEach((qNodeID) => nodesToRebind[subject][qNodeID] = { newNode: object, subclassEdgeID }); + this.subclassEdges[expanded][original].qNodes.forEach( + (qNodeID) => (nodesToRebind[subject][qNodeID] = { newNode: object, subclassEdgeID }), + ); }); }); // Create new constructed edges and aux graphs for edges that used subclass edges let auxGraphs: { [supportGraphID: string]: TrapiAuxiliaryGraph } = {}; - const edgesToRebind: { [edgeID: string]: { [originalSubject: string]: { [originalObject: string]: string /* re-bound edge ID */ } } } = {}; + const edgesToRebind: { + [edgeID: string]: { [originalSubject: string]: { [originalObject: string]: string /* re-bound edge ID */ } }; + } = {}; const edgesIDsByAuxGraphID = {}; Object.entries(this.bteGraph.edges).forEach(([edgeID, bteEdge]) => { if (edgeID.includes('expanded')) return; - const combos: {subject: string, object: string, supportGraph: string[]}[] = []; - const subjectToSupportGraphs: {[sbj: string]: Set} = { - [bteEdge.subject]: new Set(), + const combos: { subject: string; object: string; supportGraph: string[] }[] = []; + const subjectToSupportGraphs: { [sbj: string]: Set } = { + [bteEdge.subject]: new Set(), ...Object.values(nodesToRebind[bteEdge.subject] ?? {}).reduce((acc, x) => { - x.newNode in acc ? acc[x.newNode].add(x.subclassEdgeID) : acc[x.newNode] = new Set([x.subclassEdgeID]) + x.newNode in acc ? acc[x.newNode].add(x.subclassEdgeID) : (acc[x.newNode] = new Set([x.subclassEdgeID])); return acc; - }, {}) + }, {}), }; - const objectToSupportGraphs: {[obj: string]: Set} = { - [bteEdge.object]: new Set(), + const objectToSupportGraphs: { [obj: string]: Set } = { + [bteEdge.object]: new Set(), ...Object.values(nodesToRebind[bteEdge.object] ?? {}).reduce((acc, x) => { - x.newNode in acc ? acc[x.newNode].add(x.subclassEdgeID) : acc[x.newNode] = new Set([x.subclassEdgeID]); + x.newNode in acc ? acc[x.newNode].add(x.subclassEdgeID) : (acc[x.newNode] = new Set([x.subclassEdgeID])); return acc; - }, {}) + }, {}), }; for (const subject in subjectToSupportGraphs) { for (const object in objectToSupportGraphs) { if (subject == bteEdge.subject && object == bteEdge.object) continue; // no nodes are rebound - combos.push({ subject, object, supportGraph: [...subjectToSupportGraphs[subject], ...objectToSupportGraphs[object], edgeID] }); + combos.push({ + subject, + object, + supportGraph: [...subjectToSupportGraphs[subject], ...objectToSupportGraphs[object], edgeID], + }); } } - combos.forEach(({subject, object, supportGraph}) => { + combos.forEach(({ subject, object, supportGraph }) => { const boundEdgeID = `${subject}-${bteEdge.predicate.replace('biolink:', '')}-${object}-via_subclass`; let suffix = 0; while (Object.keys(auxGraphs).includes(`support${suffix}-${boundEdgeID}`)) { @@ -253,8 +263,8 @@ export default class TRAPIQueryHandler { object: object, }); boundEdge.addAdditionalAttributes('biolink:support_graphs', [supportGraphID]); - boundEdge.addAdditionalAttributes('biolink:knowledge_level', 'logical_entailment') - boundEdge.addAdditionalAttributes('biolink:agent_type', 'automated_agent') + boundEdge.addAdditionalAttributes('biolink:knowledge_level', 'logical_entailment'); + boundEdge.addAdditionalAttributes('biolink:agent_type', 'automated_agent'); boundEdge.addSource([ { resource_id: this.options.provenanceUsesServiceProvider @@ -270,7 +280,7 @@ export default class TRAPIQueryHandler { if (!edgesToRebind[edgeID]) edgesToRebind[edgeID] = {}; if (!edgesToRebind[edgeID][subject]) edgesToRebind[edgeID][subject] = {}; edgesToRebind[edgeID][subject][object] = boundEdgeID; - }) + }); }); const resultBoundEdgesWithAuxGraphs = new Set(); @@ -343,15 +353,18 @@ export default class TRAPIQueryHandler { } appendOriginalCuriesToResults(results: TrapiResult[]): void { - results.forEach(result => { + 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) { + 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 { @@ -412,8 +425,10 @@ export default class TRAPIQueryHandler { for (const nodeId in queryGraph.nodes) { // perform node expansion if (queryGraph.nodes[nodeId].ids && !this._queryUsesInferredMode()) { - const descendantsByCurie: { [curie: string]: string[] } = getDescendants(queryGraph.nodes[nodeId].ids); - let expanded = Object.values(descendantsByCurie).flat(); + const descendantsByCurie: { [curie: string]: { [descendants: string]: string } } = getDescendants( + queryGraph.nodes[nodeId].ids, + ); + let expanded = Object.values(descendantsByCurie).map(descendants => Object.keys(descendants)).flat() expanded = _.uniq([...queryGraph.nodes[nodeId].ids, ...expanded]); @@ -425,11 +440,11 @@ export default class TRAPIQueryHandler { if (foundExpandedIds) { Object.entries(descendantsByCurie).forEach(([curie, descendants]) => { - descendants.forEach((descendant) => { + Object.entries(descendants).forEach(([ descendant, source ]) => { if (queryGraph.nodes[nodeId].ids.includes(descendant)) return; if (!this.subclassEdges[descendant]) this.subclassEdges[descendant] = {}; - if (!this.subclassEdges[descendant][curie]) this.subclassEdges[descendant][curie] = []; - this.subclassEdges[descendant][curie].push(nodeId); + if (!this.subclassEdges[descendant][curie]) this.subclassEdges[descendant][curie] = { source, qNodes: [] }; + this.subclassEdges[descendant][curie].qNodes.push(nodeId); }); }); } @@ -492,11 +507,13 @@ export default class TRAPIQueryHandler { let log_msg: string; if (currentQEdge.reverse) { - log_msg = `qEdge ${currentQEdge.id} (reversed): ${currentQEdge.object.categories} > ${currentQEdge.predicate ? `${currentQEdge.predicate} > ` : '' - }${currentQEdge.subject.categories}`; + log_msg = `qEdge ${currentQEdge.id} (reversed): ${currentQEdge.object.categories} > ${ + currentQEdge.predicate ? `${currentQEdge.predicate} > ` : '' + }${currentQEdge.subject.categories}`; } else { - log_msg = `qEdge ${currentQEdge.id}: ${currentQEdge.subject.categories} > ${currentQEdge.predicate ? `${currentQEdge.predicate} > ` : '' - }${currentQEdge.object.categories}`; + log_msg = `qEdge ${currentQEdge.id}: ${currentQEdge.subject.categories} > ${ + currentQEdge.predicate ? `${currentQEdge.predicate} > ` : '' + }${currentQEdge.object.categories}`; } this.logs.push(new LogEntry('INFO', null, log_msg).getLog()); @@ -537,8 +554,9 @@ export default class TRAPIQueryHandler { }); const qEdgesLogStr = qEdgesToLog.length > 1 ? `[${qEdgesToLog.join(', ')}]` : `${qEdgesToLog.join(', ')}`; if (len > 0) { - const terminateLog = `Query Edge${len !== 1 ? 's' : ''} ${qEdgesLogStr} ${len !== 1 ? 'have' : 'has' - } no MetaKG edges. Your query terminates.`; + const terminateLog = `Query Edge${len !== 1 ? 's' : ''} ${qEdgesLogStr} ${ + len !== 1 ? 'have' : 'has' + } no MetaKG edges. Your query terminates.`; debug(terminateLog); this.logs.push(new LogEntry('WARNING', null, terminateLog).getLog()); return false; @@ -652,7 +670,8 @@ export default class TRAPIQueryHandler { new LogEntry( 'INFO', null, - `Execution Summary: (${KGNodes}) nodes / (${kgEdges}) edges / (${results}) results; (${resultQueries}/${queries}) queries${cached ? ` (${cached} cached qEdges)` : '' + `Execution Summary: (${KGNodes}) nodes / (${kgEdges}) edges / (${results}) results; (${resultQueries}/${queries}) queries${ + cached ? ` (${cached} cached qEdges)` : '' } returned results from(${sources.length}) unique API${sources.length === 1 ? 's' : ''}`, ).getLog(), new LogEntry('INFO', null, `APIs: ${sources.join(', ')} `).getLog(), diff --git a/src/types.ts b/src/types.ts index d00065a5..60d5fbf8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -9,7 +9,10 @@ export interface CompactQualifiers { } export interface SubclassEdges { - [expandedID: string]: { - [parentID: string]: string[] /* QNode IDs */ - } -} \ No newline at end of file + [expandedID: string]: { + [parentID: string]: { + source: string; + qNodes: string[]; + }; + }; +}