/** * Client for the Entropyk REST API (Axum server in demo/). * * In development, requests go through the Next.js rewrite proxy * (/api/entropyk/* → http://localhost:3030/api/* by default; see next.config.mjs). */ import type { ComponentMeta } from "./componentMeta"; const API_BASE = process.env.NEXT_PUBLIC_API_URL || "/api/entropyk"; export interface IterationInfo { iteration: number; residual_norm: number; delta_norm: number; alpha?: number | null; max_residual_index?: number | null; max_residual?: number; } export interface SimulationResult { input: string; status: "converged" | "failed" | "error" | string; convergence?: { iterations?: number; final_residual?: number; tolerance?: number; strategy?: string; iteration_history?: IterationInfo[]; converged?: boolean; status?: string; }; iterations?: number; state?: Array<{ edge?: number; edge_id?: number; pressure_bar?: number; pressure_pa?: number; enthalpy_kj_kg?: number; enthalpy_j_kg?: number; mass_flow_kg_s?: number; source?: string; target?: string; source_port?: string; target_port?: string; temperature_c?: number; saturation_temperature_c?: number; }>; performance?: { q_cooling_kw?: number | null; q_heating_kw?: number | null; compressor_power_kw?: number | null; cop?: number | null; cooling_capacity_w?: number | null; heating_capacity_w?: number | null; compressor_power_w?: number | null; pump_power_w?: number | null; cop_cooling?: number | null; cop_heating?: number | null; }; components?: Array<{ name: string; component_type: string; circuit: number; inlet?: { pressure_pa: number; enthalpy_j_kg: number }; outlet?: { pressure_pa: number; enthalpy_j_kg: number }; energy?: { heat_transfer_w: number; work_w: number }; }>; error?: string | null; failure_diagnostics?: { final_residual_norm?: number; last_residual_norm?: number; dominant_residual_index?: number; dominant_residual_value?: number; } | null; /** Degrees-of-freedom summary after topology finalize (CLI hard gate). */ dof?: DofSummary | null; } /** Degrees-of-freedom summary returned by the CLI after finalize. */ export interface DofSummary { n_equations: number; n_unknowns: number; balance: string; summary: string; } export interface SimulateResponse { ok: boolean; result?: SimulationResult; error?: string; } export interface MollierPoint { t_k: number; p_bar: number; h_liq_kj_kg: number; h_vap_kj_kg: number; } export interface MollierResponse { ok: boolean; fluid: string; saturation: MollierPoint[]; error?: string | null; } export async function fetchComponents(): Promise { const res = await fetch(`${API_BASE}/components`, { cache: "no-store" }); if (!res.ok) throw new Error(`Failed to fetch components: ${res.status}`); return res.json(); } export async function simulate(config: unknown): Promise { const healthy = await checkHealth(); if (!healthy) { throw new Error( `Entropyk API is down (${API_BASE}/health). Start ui-server: cargo run -p entropyk-demo --bin ui-server`, ); } let res: Response; try { res = await fetch(`${API_BASE}/simulate`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(config), }); } catch (e) { const detail = e instanceof Error ? e.message : String(e); throw new Error( `Cannot reach Entropyk API (${API_BASE}). ui-server may have crashed mid-solve (CoolProp). Restart it on :3030. ${detail}`, ); } if (!res.ok) { const body = (await res.text().catch(() => "")).trim(); const hint = res.status === 500 || res.status === 502 || res.status === 504 ? " — ui-server likely crashed (CoolProp race) or was restarting. Restart: cargo run -p entropyk-demo --bin ui-server" : ""; throw new Error( `Simulate request failed: ${res.status}${hint}${body ? `\n${body.slice(0, 400)}` : ""}`, ); } return res.json(); } export async function fetchMollier(fluid: string): Promise { const res = await fetch(`${API_BASE}/mollier?fluid=${encodeURIComponent(fluid)}`, { cache: "no-store" }); if (!res.ok) throw new Error(`Failed to fetch Mollier data: ${res.status}`); return res.json(); } export async function checkHealth(): Promise { try { const res = await fetch(`${API_BASE}/health`, { cache: "no-store" }); return res.ok; } catch { return false; } }