CTA / frontend /src /lib /api.ts
TheQuantEd's picture
Initial deployment: ClinicalMatch AI v2.0 β€” FHIR R4 Β· MCP (9 tools) Β· A2A workflow Β· SHARP compliance Β· 100k synthetic patients Β· Neo4j graph Β· GraphRAG chatbot
59abb4f
// Empty string = relative URLs (Docker/HF Spaces: Nginx routes /api/* to FastAPI)
// "http://localhost:8000" = direct for local dev without Docker
const BASE = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:8000";
async function req<T>(path: string, options?: RequestInit): Promise<T> {
const res = await fetch(`${BASE}${path}`, {
headers: { "Content-Type": "application/json" },
...options,
});
if (!res.ok) {
const err = await res.text();
throw new Error(`API ${res.status}: ${err}`);
}
return res.json();
}
// ── Patients ──────────────────────────────────────────────────────────────────
export const getPatients = () => req<{ patients: any[]; total: number }>("/api/v1/patients");
export const getPatient = (id: string) => req<any>(`/api/v1/patients/${id}`);
export const getPatientFhir = (id: string) => req<any>(`/api/v1/patients/${id}/fhir`);
// ── Trials ────────────────────────────────────────────────────────────────────
export const searchTrials = (condition: string, phase?: string, pageSize = 20) => {
const params = new URLSearchParams({ condition, page_size: String(pageSize) });
if (phase) params.set("phase", phase);
return req<{ trials: any[]; total: number; condition: string }>(`/api/v1/trials/search?${params}`);
};
export const getTrial = (nctId: string) => req<any>(`/api/v1/trials/${nctId}`);
export const getTrialEligiblePatients = (nctId: string) => req<any>(`/api/v1/trials/${nctId}/eligible-patients`);
export const getTrialIntelligence = (nctId: string) => req<any>(`/api/v1/trials/${nctId}/intelligence`);
// ── Matching ──────────────────────────────────────────────────────────────────
export const matchPatientToTrials = (patientId: string, condition?: string, topN = 5) => {
const params = new URLSearchParams({ top_n: String(topN) });
if (condition) params.set("condition", condition);
return req<{ patient_id: string; matches: any[]; total: number }>(`/api/v1/patients/${patientId}/match-trials?${params}`);
};
export const screenPatient = (patientId: string, nctId: string) =>
req<any>(`/api/v1/patients/${patientId}/screen/${nctId}`, { method: "POST" });
// ── A2A Workflow ──────────────────────────────────────────────────────────────
export const runWorkflow = (patientId: string, nctId?: string, condition?: string) =>
req<any>("/api/v1/workflow/run", {
method: "POST",
body: JSON.stringify({ patient_id: patientId, nct_id: nctId, condition }),
});
export const getWorkflowStatus = (workflowId: string) => req<any>(`/api/v1/workflow/${workflowId}/status`);
export const listWorkflows = () => req<{ workflows: any[] }>("/api/v1/workflows");
// ── Recruitment ───────────────────────────────────────────────────────────────
export const getKanbanBoard = () => req<Record<string, any[]>>("/api/v1/recruitment/board");
export const getRecruitmentRecords = () => req<{ records: any[] }>("/api/v1/recruitment/records");
export const createRecruitmentRecord = (data: { patient_id: string; nct_id: string; trial_title: string; match_score: number }) =>
req<any>("/api/v1/recruitment/records", { method: "POST", body: JSON.stringify(data) });
export const updateRecordStatus = (recordId: string, status: string) =>
req<any>(`/api/v1/recruitment/records/${recordId}/status`, { method: "PATCH", body: JSON.stringify({ status }) });
export const generateOutreach = (data: { patient_id: string; nct_id: string; trial_title: string; channel: string }) =>
req<any>("/api/v1/recruitment/outreach", { method: "POST", body: JSON.stringify(data) });
// ── Analytics ─────────────────────────────────────────────────────────────────
export const getKPIs = () => req<any>("/api/v1/analytics/kpi");
export const getEnrollmentFunnel = (trialId?: string) => {
const params = trialId ? `?trial_id=${trialId}` : "";
return req<{ funnel: any[] }>(`/api/v1/analytics/funnel${params}`);
};
export const getSitePerformance = () => req<{ sites: any[] }>("/api/v1/analytics/sites");
export const getDemographics = (trialId?: string) => {
const params = trialId ? `?trial_id=${trialId}` : "";
return req<any>(`/api/v1/analytics/demographics${params}`);
};
export const getTimeline = (days = 30) => req<{ timeline: any[] }>(`/api/v1/analytics/timeline?days=${days}`);
export const getMapData = () => req<{ sites: any[]; patient_clusters: any[] }>("/api/v1/map/data");
export const getGraphStats = () => req<any>("/api/v1/graph/stats");
export const getGraphPatients = (condition?: string, limit = 200) => {
const params = new URLSearchParams({ limit: String(limit) });
if (condition) params.set("condition", condition);
return req<{ patients: any[]; total: number }>(`/api/v1/graph/patients?${params}`);
};
// ── Clinical Intake ───────────────────────────────────────────────────────────
export interface IntakeLabs {
hemoglobin?: number; // g/dL
wbc?: number; // Γ—10⁹/L
anc?: number; // Γ—10⁹/L
platelets?: number; // Γ—10⁹/L
creatinine?: number; // ΞΌmol/L
egfr?: number; // mL/min/1.73mΒ²
bilirubin?: number; // ΞΌmol/L
alt?: number; // U/L
ast?: number; // U/L
albumin?: number; // g/dL
}
export interface IntakePayload {
condition: string;
age?: number;
sex?: string;
ecog?: number;
stage?: string;
biomarkers?: string[];
labs?: IntakeLabs;
prior_chemo?: boolean;
prior_radiation?: boolean;
prior_surgery?: boolean;
medications?: string[];
save_to_graph?: boolean;
}
export const submitIntake = (data: IntakePayload) =>
req<{ condition: string; matches: any[]; total: number; patient_id?: string }>(
"/api/v1/intake/match", { method: "POST", body: JSON.stringify(data) }
);
export const getBiomarkerRegistry = () =>
req<{ biomarkers: { id: string; label: string }[] }>("/api/v1/intake/biomarkers");
// ── Graph RAG ─────────────────────────────────────────────────────────────────
export const graphQuery = (question: string) =>
req<{ response: string }>("/api/v1/graph/query", { method: "POST", body: JSON.stringify({ question }) });
// ── Streaming A2A Workflow ────────────────────────────────────────────────────
export const startWorkflow = (patientId: string, nctId?: string, condition?: string) =>
req<{ workflow_id: string; status: string; stream_url: string }>("/api/v1/workflow/start", {
method: "POST",
body: JSON.stringify({ patient_id: patientId, nct_id: nctId, condition }),
});
export const streamWorkflow = (workflowId: string, onEvent: (evt: any) => void, onDone: () => void) => {
const url = `${BASE}/api/v1/workflow/${workflowId}/stream`;
const es = new EventSource(url);
es.onmessage = (e) => {
if (e.data === "[DONE]") { es.close(); onDone(); return; }
try { onEvent(JSON.parse(e.data)); } catch {}
};
es.onerror = () => { es.close(); onDone(); };
return () => es.close();
};
// ── Consent & Scheduling ─────────────────────────────────────────────────────
export const getConsents = (patientId?: string) => {
const params = patientId ? `?patient_id=${patientId}` : "";
return req<{ consents: any[] }>(`/api/v1/consent${params}`);
};
export const getConsentStats = () => req<any>("/api/v1/consent/stats");
export const updateConsentStatus = (consentId: string, status: string, notes?: string) =>
req<any>(`/api/v1/consent/${consentId}/status`, {
method: "PATCH",
body: JSON.stringify({ status, notes }),
});
export const getAppointments = (patientId?: string) => {
const params = patientId ? `?patient_id=${patientId}` : "";
return req<{ appointments: any[] }>(`/api/v1/appointments${params}`);
};
export const confirmAppointment = (apptId: string) =>
req<any>(`/api/v1/appointments/${apptId}/confirm`, { method: "PATCH" });
// ── Health ────────────────────────────────────────────────────────────────────
export const getHealth = () => req<any>("/health");