| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| import { eq, inArray } from "drizzle-orm"; |
| import { |
| drugNodes, |
| targetNodes, |
| outcomeNodes, |
| drugTargetsProtein, |
| drugAffectsOutcome, |
| db, |
| } from "@workspace/db"; |
| import { enforceBudget } from "./budget-guard.ts"; |
| import { fingerprintParams, recordCall } from "./telemetry.ts"; |
| import type { |
| KnowledgeAdapter, |
| KnowledgeItem, |
| KnowledgeQuery, |
| KnowledgeResult, |
| } from "./types.ts"; |
|
|
| interface DrugParams { |
| drug_id?: string; |
| target_id?: string; |
| outcome_id?: string; |
| } |
|
|
| function readParams(p: Record<string, unknown>): DrugParams { |
| return { |
| drug_id: typeof p["drug_id"] === "string" ? (p["drug_id"] as string) : undefined, |
| target_id: typeof p["target_id"] === "string" ? (p["target_id"] as string) : undefined, |
| outcome_id: |
| typeof p["outcome_id"] === "string" ? (p["outcome_id"] as string) : undefined, |
| }; |
| } |
|
|
| export const drugAdapter: KnowledgeAdapter = { |
| kind: "drug_network", |
| status: "real", |
| async query(q: KnowledgeQuery): Promise<KnowledgeResult> { |
| enforceBudget(q.capabilityId, "drug_network"); |
| const params = readParams(q.params ?? {}); |
| const limit = Math.min(q.limit ?? 50, 500); |
| const fp = fingerprintParams({ ...params, limit }); |
| const t0 = Date.now(); |
| let error: string | null = null; |
| let items: KnowledgeItem[] = []; |
|
|
| try { |
| if (!params.drug_id && !params.target_id && !params.outcome_id) { |
| |
| |
| throw new Error( |
| "drug.adapter requires at least one of drug_id / target_id / outcome_id", |
| ); |
| } |
|
|
| |
| if (params.drug_id) { |
| const targets = await db |
| .select({ |
| drugId: drugTargetsProtein.drugId, |
| targetId: drugTargetsProtein.targetId, |
| sourceDb: drugTargetsProtein.sourceDb, |
| confidence: drugTargetsProtein.confidence, |
| target: targetNodes, |
| }) |
| .from(drugTargetsProtein) |
| .leftJoin( |
| targetNodes, |
| eq(drugTargetsProtein.targetId, targetNodes.targetId), |
| ) |
| .where(eq(drugTargetsProtein.drugId, params.drug_id)) |
| .limit(limit); |
|
|
| for (const row of targets) { |
| items.push({ |
| id: `${row.drugId}::${row.targetId}::${row.sourceDb}`, |
| score: row.confidence, |
| payload: { |
| edge_kind: "drug_targets_protein", |
| drug_id: row.drugId, |
| target_id: row.targetId, |
| source_db: row.sourceDb, |
| confidence: row.confidence, |
| target: row.target, |
| }, |
| origin: "pg:drug_targets_protein", |
| }); |
| } |
|
|
| const outcomes = await db |
| .select({ |
| drugId: drugAffectsOutcome.drugId, |
| outcomeId: drugAffectsOutcome.outcomeId, |
| sourceDb: drugAffectsOutcome.sourceDb, |
| confidence: drugAffectsOutcome.confidence, |
| outcome: outcomeNodes, |
| }) |
| .from(drugAffectsOutcome) |
| .leftJoin( |
| outcomeNodes, |
| eq(drugAffectsOutcome.outcomeId, outcomeNodes.outcomeId), |
| ) |
| .where(eq(drugAffectsOutcome.drugId, params.drug_id)) |
| .limit(limit); |
|
|
| for (const row of outcomes) { |
| items.push({ |
| id: `${row.drugId}::${row.outcomeId}::${row.sourceDb}`, |
| score: row.confidence, |
| payload: { |
| edge_kind: "drug_affects_outcome", |
| drug_id: row.drugId, |
| outcome_id: row.outcomeId, |
| source_db: row.sourceDb, |
| confidence: row.confidence, |
| outcome: row.outcome, |
| }, |
| origin: "pg:drug_affects_outcome", |
| }); |
| } |
| } |
|
|
| |
| if (params.target_id) { |
| const drugs = await db |
| .select({ |
| drugId: drugTargetsProtein.drugId, |
| targetId: drugTargetsProtein.targetId, |
| sourceDb: drugTargetsProtein.sourceDb, |
| confidence: drugTargetsProtein.confidence, |
| drug: drugNodes, |
| }) |
| .from(drugTargetsProtein) |
| .leftJoin(drugNodes, eq(drugTargetsProtein.drugId, drugNodes.drugId)) |
| .where(eq(drugTargetsProtein.targetId, params.target_id)) |
| .limit(limit); |
| for (const row of drugs) { |
| items.push({ |
| id: `${row.drugId}::${row.targetId}::${row.sourceDb}`, |
| score: row.confidence, |
| payload: { |
| edge_kind: "target_targeted_by_drug", |
| drug_id: row.drugId, |
| target_id: row.targetId, |
| source_db: row.sourceDb, |
| confidence: row.confidence, |
| drug: row.drug, |
| }, |
| origin: "pg:drug_targets_protein", |
| }); |
| } |
|
|
| |
| |
| |
| const tracedDrugIds = Array.from( |
| new Set(drugs.map((d) => d.drugId).filter((x): x is string => !!x)), |
| ); |
| if (tracedDrugIds.length > 0) { |
| const outcomes = await db |
| .select({ |
| drugId: drugAffectsOutcome.drugId, |
| outcomeId: drugAffectsOutcome.outcomeId, |
| sourceDb: drugAffectsOutcome.sourceDb, |
| confidence: drugAffectsOutcome.confidence, |
| outcome: outcomeNodes, |
| }) |
| .from(drugAffectsOutcome) |
| .leftJoin( |
| outcomeNodes, |
| eq(drugAffectsOutcome.outcomeId, outcomeNodes.outcomeId), |
| ) |
| .where(inArray(drugAffectsOutcome.drugId, tracedDrugIds)) |
| .limit(limit); |
| for (const row of outcomes) { |
| items.push({ |
| id: `${row.drugId}::${row.outcomeId}::${row.sourceDb}::via_target`, |
| score: row.confidence, |
| payload: { |
| edge_kind: "target_to_outcome_via_drug", |
| target_id: params.target_id, |
| drug_id: row.drugId, |
| outcome_id: row.outcomeId, |
| source_db: row.sourceDb, |
| confidence: row.confidence, |
| outcome: row.outcome, |
| }, |
| origin: "pg:drug_affects_outcome", |
| }); |
| } |
| } |
| } |
|
|
| |
| if (params.outcome_id) { |
| const drugs = await db |
| .select({ |
| drugId: drugAffectsOutcome.drugId, |
| outcomeId: drugAffectsOutcome.outcomeId, |
| sourceDb: drugAffectsOutcome.sourceDb, |
| confidence: drugAffectsOutcome.confidence, |
| drug: drugNodes, |
| }) |
| .from(drugAffectsOutcome) |
| .leftJoin(drugNodes, eq(drugAffectsOutcome.drugId, drugNodes.drugId)) |
| .where(eq(drugAffectsOutcome.outcomeId, params.outcome_id)) |
| .limit(limit); |
| for (const row of drugs) { |
| items.push({ |
| id: `${row.drugId}::${row.outcomeId}::${row.sourceDb}`, |
| score: row.confidence, |
| payload: { |
| edge_kind: "outcome_caused_by_drug", |
| drug_id: row.drugId, |
| outcome_id: row.outcomeId, |
| source_db: row.sourceDb, |
| confidence: row.confidence, |
| drug: row.drug, |
| }, |
| origin: "pg:drug_affects_outcome", |
| }); |
| } |
| } |
| } catch (err) { |
| error = err instanceof Error ? err.message : String(err); |
| throw err; |
| } finally { |
| void recordCall({ |
| adapter: "drug_network", |
| capabilityId: q.capabilityId, |
| latencyMs: Date.now() - t0, |
| hitCount: items.length, |
| cacheHit: null, |
| error, |
| paramsFingerprint: fp, |
| }); |
| } |
| return { |
| kind: "drug_network", |
| items: items.slice(0, limit), |
| cursor: null, |
| sourceMetadata: { |
| primarySource: "pg:drug_network", |
| limit, |
| filters: params, |
| }, |
| }; |
| }, |
| }; |
|
|