UNPKG

claude-flow

Version:

Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration

630 lines (552 loc) • 19.4 kB
/** * GNN Bridge - Healthcare Clinical Plugin * * Provides Graph Neural Network capabilities for clinical pathway * analysis and drug interaction detection. Integrates with * ruvector-gnn-wasm for efficient graph-based reasoning. * * Use Cases: * - Clinical pathway recommendations * - Drug interaction network analysis * - Comorbidity pattern detection * - Treatment outcome prediction */ import type { GNNBridge, GNNConfig, GNNNode, GNNEdge, GNNPathResult, GNNInteractionResult, DrugInteraction, InteractionSeverity, ClinicalPathway, Logger, } from '../types.js'; /** * Default logger */ const defaultLogger: Logger = { debug: (msg: string, meta?: Record<string, unknown>) => console.debug(`[gnn-bridge] ${msg}`, meta), info: (msg: string, meta?: Record<string, unknown>) => console.info(`[gnn-bridge] ${msg}`, meta), warn: (msg: string, meta?: Record<string, unknown>) => console.warn(`[gnn-bridge] ${msg}`, meta), error: (msg: string, meta?: Record<string, unknown>) => console.error(`[gnn-bridge] ${msg}`, meta), }; /** * WASM module interface for ruvector-gnn-wasm */ interface GNNWasmModule { create_graph(numNodes: number, numEdges: number, featureDim: number): number; add_node(graphPtr: number, nodeId: number, features: Float32Array): boolean; add_edge(graphPtr: number, source: number, target: number, edgeType: number, weight: number): boolean; forward(graphPtr: number, nodeFeatures: Float32Array): Float32Array; predict_path(graphPtr: number, startNode: number, endNode: number, maxHops: number): Uint32Array; analyze_subgraph(graphPtr: number, nodeIds: Uint32Array): Float32Array; free_graph(graphPtr: number): void; memory: { buffer: ArrayBuffer }; } /** * Drug interaction graph with known interactions */ class DrugInteractionGraph { private interactions: Map<string, DrugInteraction[]> = new Map(); private readonly severityOrder: InteractionSeverity[] = ['contraindicated', 'major', 'moderate', 'minor']; constructor() { this.loadKnownInteractions(); } /** * Load known drug interactions * In production, this would load from a clinical database */ private loadKnownInteractions(): void { // Sample known drug-drug interactions const knownInteractions: DrugInteraction[] = [ { drug1: 'warfarin', drug2: 'aspirin', severity: 'major', description: 'Increased risk of bleeding when warfarin is combined with aspirin', mechanism: 'Both drugs affect blood clotting through different mechanisms', clinicalEffect: 'Enhanced anticoagulation, increased bleeding risk', management: 'Monitor INR closely, consider alternative antiplatelet agent', }, { drug1: 'metformin', drug2: 'contrast_dye', severity: 'major', description: 'Risk of lactic acidosis with iodinated contrast media', mechanism: 'Contrast-induced nephropathy may impair metformin clearance', clinicalEffect: 'Potential for severe metabolic acidosis', management: 'Hold metformin 48 hours before and after contrast administration', }, { drug1: 'simvastatin', drug2: 'clarithromycin', severity: 'contraindicated', description: 'Significantly increased risk of myopathy and rhabdomyolysis', mechanism: 'Clarithromycin inhibits CYP3A4, increasing simvastatin levels', clinicalEffect: 'Muscle damage, potential kidney failure', management: 'Avoid combination, use azithromycin if macrolide needed', }, { drug1: 'lisinopril', drug2: 'potassium', severity: 'moderate', description: 'Increased risk of hyperkalemia', mechanism: 'ACE inhibitors reduce aldosterone, preserving potassium', clinicalEffect: 'Elevated serum potassium levels', management: 'Monitor potassium levels regularly', }, { drug1: 'ssri', drug2: 'maoi', severity: 'contraindicated', description: 'Risk of serotonin syndrome', mechanism: 'Combined serotonergic activity', clinicalEffect: 'Potentially fatal serotonin toxicity', management: 'Allow 2-week washout period between drugs', }, ]; for (const interaction of knownInteractions) { const key1 = this.getInteractionKey(interaction.drug1, interaction.drug2); const key2 = this.getInteractionKey(interaction.drug2, interaction.drug1); if (!this.interactions.has(key1)) { this.interactions.set(key1, []); } this.interactions.get(key1)!.push(interaction); if (!this.interactions.has(key2)) { this.interactions.set(key2, []); } this.interactions.get(key2)!.push({ ...interaction, drug1: interaction.drug2, drug2: interaction.drug1, }); } } /** * Check for drug interactions */ checkInteractions(medications: string[], severityFilter: string = 'all'): DrugInteraction[] { const results: DrugInteraction[] = []; const normalized = medications.map(m => m.toLowerCase().trim()); for (let i = 0; i < normalized.length; i++) { for (let j = i + 1; j < normalized.length; j++) { const drug1 = normalized[i]!; const drug2 = normalized[j]!; const key = this.getInteractionKey(drug1, drug2); const interactions = this.interactions.get(key); if (interactions) { for (const interaction of interactions) { if (severityFilter === 'all' || this.meetsSeverityFilter(interaction.severity, severityFilter)) { results.push(interaction); } } } } } // Sort by severity results.sort((a, b) => { return this.severityOrder.indexOf(a.severity) - this.severityOrder.indexOf(b.severity); }); return results; } private getInteractionKey(drug1: string, drug2: string): string { return [drug1.toLowerCase(), drug2.toLowerCase()].sort().join('::'); } private meetsSeverityFilter(severity: InteractionSeverity, filter: string): boolean { if (filter === 'all') return true; const filterIndex = this.severityOrder.indexOf(filter as InteractionSeverity); const severityIndex = this.severityOrder.indexOf(severity); return severityIndex <= filterIndex; } } /** * Clinical pathway graph for treatment recommendations */ class ClinicalPathwayGraph { private pathways: Map<string, ClinicalPathway> = new Map(); constructor() { this.loadStandardPathways(); } /** * Load standard clinical pathways * In production, load from clinical guideline database */ private loadStandardPathways(): void { const samplePathways: ClinicalPathway[] = [ { id: 'dm2-management', name: 'Type 2 Diabetes Management', diagnosis: 'E11', version: '2024.1', evidenceLevel: 'systematic-review', steps: [ { id: 'dm2-1', name: 'Initial Assessment', description: 'Complete metabolic panel, HbA1c, lipid panel', type: 'assessment', timing: 'Day 1', }, { id: 'dm2-2', name: 'Lifestyle Modification', description: 'Diet counseling, exercise prescription', type: 'intervention', timing: 'Weeks 1-12', prerequisites: ['dm2-1'], }, { id: 'dm2-3', name: 'Metformin Initiation', description: 'Start metformin if HbA1c > 6.5%', type: 'decision', timing: 'Week 1', prerequisites: ['dm2-1'], }, { id: 'dm2-4', name: 'Glycemic Monitoring', description: 'Regular HbA1c monitoring every 3 months', type: 'monitoring', timing: 'Ongoing', prerequisites: ['dm2-2', 'dm2-3'], }, ], }, { id: 'htn-management', name: 'Hypertension Management', diagnosis: 'I10', version: '2024.1', evidenceLevel: 'rct', steps: [ { id: 'htn-1', name: 'Blood Pressure Confirmation', description: 'Confirm elevated BP with multiple readings', type: 'assessment', timing: 'Days 1-7', }, { id: 'htn-2', name: 'Risk Stratification', description: 'Assess cardiovascular risk factors', type: 'assessment', timing: 'Day 1', }, { id: 'htn-3', name: 'Lifestyle Modification', description: 'DASH diet, sodium restriction, exercise', type: 'intervention', timing: 'Weeks 1-8', prerequisites: ['htn-1', 'htn-2'], }, { id: 'htn-4', name: 'ACE Inhibitor or ARB', description: 'First-line pharmacotherapy', type: 'decision', timing: 'Week 1-2', prerequisites: ['htn-2'], }, ], }, ]; for (const pathway of samplePathways) { this.pathways.set(pathway.diagnosis, pathway); this.pathways.set(pathway.id, pathway); } } /** * Find pathway by diagnosis code */ findPathway(diagnosis: string): ClinicalPathway | undefined { // Try exact match first let pathway = this.pathways.get(diagnosis); if (pathway) return pathway; // Try ICD-10 category match (first 3 characters) const category = diagnosis.substring(0, 3); pathway = this.pathways.get(category); if (pathway) return pathway; // Search by diagnosis prefix for (const [key, value] of this.pathways) { if (diagnosis.startsWith(key) || key.startsWith(diagnosis)) { return value; } } return undefined; } /** * Get all available pathways */ getAllPathways(): ClinicalPathway[] { const seen = new Set<string>(); const results: ClinicalPathway[] = []; for (const pathway of this.pathways.values()) { if (!seen.has(pathway.id)) { seen.add(pathway.id); results.push(pathway); } } return results; } } /** * Healthcare GNN Bridge implementation */ export class HealthcareGNNBridge implements GNNBridge { private wasmModule: GNNWasmModule | null = null; private graphPtr: number = 0; private config: GNNConfig; private logger: Logger; private nodes: Map<string, GNNNode> = new Map(); private edges: GNNEdge[] = []; private nodeIdToIndex: Map<string, number> = new Map(); private drugInteractionGraph: DrugInteractionGraph; private clinicalPathwayGraph: ClinicalPathwayGraph; public initialized = false; constructor(config?: Partial<GNNConfig>, logger?: Logger) { this.config = { hiddenDimensions: config?.hiddenDimensions ?? 256, numLayers: config?.numLayers ?? 3, dropout: config?.dropout ?? 0.1, aggregationType: config?.aggregationType ?? 'mean', }; this.logger = logger ?? defaultLogger; this.drugInteractionGraph = new DrugInteractionGraph(); this.clinicalPathwayGraph = new ClinicalPathwayGraph(); } /** * Initialize the GNN bridge */ async initialize(config?: GNNConfig): Promise<void> { if (config) { this.config = { ...this.config, ...config }; } try { const wasmPath = await this.resolveWasmPath(); if (wasmPath) { this.wasmModule = await this.loadWasmModule(wasmPath); this.logger.info('GNN WASM module initialized', { hiddenDimensions: this.config.hiddenDimensions, numLayers: this.config.numLayers, }); } else { this.logger.warn('WASM module not available, using fallback implementation'); } this.initialized = true; } catch (error) { this.logger.warn('Failed to initialize WASM, using fallback', { error: error instanceof Error ? error.message : String(error), }); this.initialized = true; } } /** * Load a graph into the GNN */ async loadGraph(nodes: GNNNode[], edges: GNNEdge[]): Promise<void> { if (!this.initialized) { throw new Error('GNN bridge not initialized'); } // Clear existing graph this.nodes.clear(); this.edges = []; this.nodeIdToIndex.clear(); // Index nodes let idx = 0; for (const node of nodes) { this.nodes.set(node.id, node); this.nodeIdToIndex.set(node.id, idx++); } // Store edges this.edges = edges; if (this.wasmModule) { // Create WASM graph const featureDim = nodes[0]?.features.length ?? 128; this.graphPtr = this.wasmModule.create_graph(nodes.length, edges.length, featureDim); // Add nodes for (const node of nodes) { const nodeIdx = this.nodeIdToIndex.get(node.id)!; this.wasmModule.add_node(this.graphPtr, nodeIdx, new Float32Array(node.features)); } // Add edges for (const edge of edges) { const sourceIdx = this.nodeIdToIndex.get(edge.source); const targetIdx = this.nodeIdToIndex.get(edge.target); if (sourceIdx !== undefined && targetIdx !== undefined) { this.wasmModule.add_edge(this.graphPtr, sourceIdx, targetIdx, 0, edge.weight ?? 1.0); } } } this.logger.info('Graph loaded', { nodes: nodes.length, edges: edges.length }); } /** * Predict optimal pathway between nodes */ async predictPathway( startNode: string, endNode: string, constraints?: Record<string, unknown> ): Promise<GNNPathResult> { if (!this.initialized) { throw new Error('GNN bridge not initialized'); } const startTime = performance.now(); if (this.wasmModule && this.graphPtr) { const startIdx = this.nodeIdToIndex.get(startNode); const endIdx = this.nodeIdToIndex.get(endNode); if (startIdx !== undefined && endIdx !== undefined) { const maxHops = (constraints?.maxHops as number) ?? 10; const pathIndices = this.wasmModule.predict_path(this.graphPtr, startIdx, endIdx, maxHops); const path = Array.from(pathIndices) .map(idx => this.getNodeIdByIndex(idx)) .filter((id): id is string => id !== undefined); return { path, confidence: 0.85, alternativePaths: [], riskScore: 0.2, }; } } // Fallback: BFS path finding const path = this.bfsPath(startNode, endNode, constraints); const duration = performance.now() - startTime; this.logger.debug('Pathway predicted', { startNode, endNode, pathLength: path.length, durationMs: duration }); return { path, confidence: path.length > 0 ? 0.75 : 0, alternativePaths: [], riskScore: this.calculatePathRisk(path), }; } /** * Analyze interactions between nodes */ async analyzeInteractions(nodeIds: string[]): Promise<GNNInteractionResult> { if (!this.initialized) { throw new Error('GNN bridge not initialized'); } // Use drug interaction graph for medication analysis const drugInteractions = this.drugInteractionGraph.checkInteractions(nodeIds); const interactions = drugInteractions.map(di => ({ nodes: [di.drug1, di.drug2], type: 'drug-drug', strength: this.severityToStrength(di.severity), direction: 'bidirectional', })); const riskFactors = drugInteractions .filter(di => di.severity === 'major' || di.severity === 'contraindicated') .map(di => `${di.drug1} + ${di.drug2}: ${di.description}`); const recommendations = drugInteractions.map(di => di.management ?? 'Monitor closely'); return { interactions, riskFactors, recommendations: [...new Set(recommendations)], }; } /** * Get clinical pathway for a diagnosis */ getClinicalPathway(diagnosis: string): ClinicalPathway | undefined { return this.clinicalPathwayGraph.findPathway(diagnosis); } /** * Check drug interactions */ checkDrugInteractions(medications: string[], severityFilter: string = 'all'): DrugInteraction[] { return this.drugInteractionGraph.checkInteractions(medications, severityFilter); } /** * Cleanup resources */ destroy(): void { if (this.wasmModule && this.graphPtr) { this.wasmModule.free_graph(this.graphPtr); } this.nodes.clear(); this.edges = []; this.nodeIdToIndex.clear(); this.initialized = false; } // Private methods private async resolveWasmPath(): Promise<string | null> { try { // Dynamic import with type assertion for optional WASM package const module = await import(/* webpackIgnore: true */ 'ruvector-gnn-wasm' as string) as { default?: string }; return module.default ?? null; } catch { return null; } } private async loadWasmModule(wasmPath: string): Promise<GNNWasmModule> { const module = await import(wasmPath); await module.default(); return module as GNNWasmModule; } private getNodeIdByIndex(index: number): string | undefined { for (const [id, idx] of this.nodeIdToIndex) { if (idx === index) return id; } return undefined; } private bfsPath(start: string, end: string, constraints?: Record<string, unknown>): string[] { const visited = new Set<string>(); const queue: Array<{ node: string; path: string[] }> = [{ node: start, path: [start] }]; const excludeNodes = new Set((constraints?.excludeNodes as string[]) ?? []); const maxHops = (constraints?.maxHops as number) ?? 10; while (queue.length > 0) { const current = queue.shift()!; if (current.node === end) { return current.path; } if (current.path.length > maxHops) continue; if (visited.has(current.node)) continue; visited.add(current.node); // Find adjacent nodes for (const edge of this.edges) { let neighbor: string | null = null; if (edge.source === current.node) neighbor = edge.target; if (edge.target === current.node) neighbor = edge.source; if (neighbor && !visited.has(neighbor) && !excludeNodes.has(neighbor)) { queue.push({ node: neighbor, path: [...current.path, neighbor] }); } } } return []; } private calculatePathRisk(path: string[]): number { if (path.length === 0) return 1.0; let risk = 0; for (let i = 0; i < path.length - 1; i++) { const edge = this.edges.find( e => (e.source === path[i] && e.target === path[i + 1]) || (e.target === path[i] && e.source === path[i + 1]) ); if (edge && edge.weight !== undefined) { risk += 1 - edge.weight; } } return Math.min(risk / path.length, 1.0); } private severityToStrength(severity: InteractionSeverity): number { switch (severity) { case 'contraindicated': return 1.0; case 'major': return 0.8; case 'moderate': return 0.5; case 'minor': return 0.2; default: return 0.5; } } } /** * Create a new GNN bridge instance */ export function createGNNBridge(config?: Partial<GNNConfig>, logger?: Logger): HealthcareGNNBridge { return new HealthcareGNNBridge(config, logger); } export default HealthcareGNNBridge;