PharmAI / frontend /src /lib /api.ts
Hattie's picture
Initial PharmAI deployment
9316888
Raw
History Blame Contribute Delete
7 kB
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;
}
/** Check ADMET CYP backend health */
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;
}
}
/** Check PBPK backend health (same server, different endpoint) */
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;
}
}
/** Predict ADMET via API */
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();
}
/** Predict ADMET properties only */
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();
}
/** Predict Clearance */
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();
}
/** Run full PBPK simulation */
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();
}
/** Load NCA CSV results */
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;
}
/** Load concentration CSV results */
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;
}
/** Try API first, fallback to sample data for ADMET */
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 {
// fallback to sample
}
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" };
}