| import { |
| PredictionResult, |
| EndpointResult, |
| SAMPLE_PREDICTIONS, |
| } from "@/data/sampleData"; |
|
|
| const API_BASE_URL = |
| import.meta.env.VITE_API_BASE_URL || "http://localhost:8000"; |
|
|
| export type ConnectionStatus = "connected" | "disconnected" | "checking"; |
|
|
| export interface ApiHealthResponse { |
| status: string; |
| model_loaded?: boolean; |
| model_version?: string | null; |
| num_targets?: number; |
| admet_loaded?: boolean; |
| clearance_loaded?: boolean; |
| } |
|
|
| export interface ApiPredictResponse { |
| smiles: string; |
| predictions: Record<string, EndpointResult>; |
| meta: { model_version: string }; |
| } |
|
|
| export interface PBPKSimulateResponse { |
| status: string; |
| message: string; |
| drug_name: string; |
| drug_data: Record<string, unknown>; |
| result_paths: { |
| nca_csv: string; |
| conc_csv: string; |
| pbpk_plot: string; |
| acat_plot: string; |
| }; |
| } |
|
|
| export interface ADMETFeatures { |
| molecular_weight: number; |
| pKa: number; |
| logP: number; |
| solubility: number; |
| permeability: number; |
| fu_in_vitro: number; |
| } |
|
|
| export interface ClearanceResult { |
| SMILES: string; |
| Clint: number; |
| } |
|
|
| |
| export async function checkAdmetHealth(): Promise<ApiHealthResponse | null> { |
| try { |
| const res = await fetch(`${API_BASE_URL}/api/health`, { |
| method: "GET", |
| signal: AbortSignal.timeout(3000), |
| }); |
| if (!res.ok) return null; |
| return await res.json(); |
| } catch { |
| return null; |
| } |
| } |
|
|
| |
| export async function checkPbpkHealth(): Promise<ApiHealthResponse | null> { |
| try { |
| const res = await fetch(`${API_BASE_URL}/api/status`, { |
| method: "GET", |
| signal: AbortSignal.timeout(3000), |
| }); |
| if (!res.ok) return null; |
| return await res.json(); |
| } catch { |
| return null; |
| } |
| } |
|
|
| |
| export async function predictAdmetFromApi( |
| smiles: string, |
| threshold = 0.5 |
| ): Promise<ApiPredictResponse> { |
| const res = await fetch(`${API_BASE_URL}/api/predict`, { |
| method: "POST", |
| headers: { "Content-Type": "application/json" }, |
| body: JSON.stringify({ smiles, threshold }), |
| }); |
| if (!res.ok) { |
| const err = await res.json().catch(() => ({ detail: "Unknown error" })); |
| throw new Error(err.detail || `API error: ${res.status}`); |
| } |
| return await res.json(); |
| } |
|
|
| |
| export async function predictAdmetProperties( |
| smiles: string |
| ): Promise<ADMETFeatures> { |
| const res = await fetch(`${API_BASE_URL}/api/predict/admet`, { |
| method: "POST", |
| headers: { "Content-Type": "application/json" }, |
| body: JSON.stringify({ smiles }), |
| }); |
| if (!res.ok) { |
| const err = await res.json().catch(() => ({ detail: "Unknown error" })); |
| throw new Error(err.detail || `ADMET prediction error: ${res.status}`); |
| } |
| return await res.json(); |
| } |
|
|
| |
| export async function predictClearance( |
| smiles: string |
| ): Promise<ClearanceResult> { |
| const res = await fetch(`${API_BASE_URL}/api/predict/clearance`, { |
| method: "POST", |
| headers: { "Content-Type": "application/json" }, |
| body: JSON.stringify({ smiles }), |
| }); |
| if (!res.ok) { |
| const err = await res.json().catch(() => ({ detail: "Unknown error" })); |
| throw new Error(err.detail || `Clearance prediction error: ${res.status}`); |
| } |
| return await res.json(); |
| } |
|
|
| |
| export async function runPbpkSimulation(params: { |
| smiles: string; |
| drug_name: string; |
| use_admet: boolean; |
| use_clearance: boolean; |
| db_features: Record<string, number | null> | null; |
| dose: number; |
| dose_unit: string; |
| body_weight?: number; |
| }): Promise<PBPKSimulateResponse> { |
| const res = await fetch(`${API_BASE_URL}/api/pbpk/simulate`, { |
| method: "POST", |
| headers: { "Content-Type": "application/json" }, |
| body: JSON.stringify(params), |
| }); |
| if (!res.ok) { |
| const err = await res.json().catch(() => ({ detail: "Unknown error" })); |
| throw new Error(err.detail || `PBPK simulation error: ${res.status}`); |
| } |
| return await res.json(); |
| } |
|
|
| |
| export async function loadNCAResults( |
| filePath: string |
| ): Promise<Record<string, number>> { |
| const res = await fetch(`${API_BASE_URL}/${filePath}`); |
| if (!res.ok) throw new Error("Failed to load NCA results"); |
| const csvText = await res.text(); |
| const lines = csvText.split("\n"); |
| const headers = lines[0].split(",").map((h) => h.replace(/"/g, "").trim()); |
| if (lines.length < 2) return {}; |
| const values = lines[1].split(",").map((v) => v.trim()); |
| const result: Record<string, number> = {}; |
| headers.forEach((h, i) => { |
| result[h] = parseFloat(values[i]) || 0; |
| }); |
| return result; |
| } |
|
|
| |
| export async function loadConcentrationResults(filePath: string): Promise<{ |
| time: number[]; |
| plasma_conc: number[]; |
| liver_cell_conc: number[]; |
| kidney_cell_conc: number[]; |
| brain_cell_conc: number[]; |
| muscle_cell_conc: number[]; |
| fat_cell_conc: number[]; |
| }> { |
| const res = await fetch(`${API_BASE_URL}/${filePath}`); |
| if (!res.ok) throw new Error("Failed to load concentration results"); |
| const csvText = await res.text(); |
| const lines = csvText.split("\n"); |
| const headers = lines[0].split(",").map((h) => h.replace(/"/g, "").trim()); |
|
|
| const data = { |
| time: [] as number[], |
| plasma_conc: [] as number[], |
| liver_cell_conc: [] as number[], |
| kidney_cell_conc: [] as number[], |
| brain_cell_conc: [] as number[], |
| muscle_cell_conc: [] as number[], |
| fat_cell_conc: [] as number[], |
| }; |
|
|
| const getIdx = (name: string) => headers.indexOf(name); |
|
|
| for (let i = 1; i < lines.length; i++) { |
| const line = lines[i].trim(); |
| if (!line) continue; |
| const vals = line.split(",").map((v) => parseFloat(v.trim()) || 0); |
| data.time.push(vals[0]); |
| data.plasma_conc.push(vals[getIdx("plasma_conc")] || 0); |
| data.liver_cell_conc.push(vals[getIdx("liver_cell_conc")] || 0); |
| data.kidney_cell_conc.push(vals[getIdx("kidneys_cell_conc")] || 0); |
| data.brain_cell_conc.push(vals[getIdx("brain_cell_conc")] || 0); |
| data.muscle_cell_conc.push(vals[getIdx("muscle_cell_conc")] || 0); |
| data.fat_cell_conc.push(vals[getIdx("fat_cell_conc")] || 0); |
| } |
| return data; |
| } |
|
|
| |
| export async function predictAdmet(smiles: string): Promise<{ |
| result: PredictionResult | null; |
| source: "api" | "sample" | "not_found"; |
| error?: string; |
| }> { |
| try { |
| const apiResult = await predictAdmetFromApi(smiles); |
| return { |
| result: { |
| smiles: apiResult.smiles, |
| name: apiResult.smiles, |
| pred: apiResult.predictions, |
| meta: apiResult.meta, |
| }, |
| source: "api", |
| }; |
| } catch { |
| |
| } |
| const trimmed = smiles.trim().toLowerCase(); |
| const found = SAMPLE_PREDICTIONS.find( |
| (p) => p.smiles.toLowerCase() === trimmed |
| ); |
| if (found) return { result: found, source: "sample" }; |
| return { result: null, source: "not_found" }; |
| } |