// NephroScreen frontend. Same-origin API by default; override API_BASE when the
// frontend is hosted separately from the backend (e.g. Vercel + Render).
const API_BASE = window.NEPHRO_API_BASE || "";
const EXAMPLE = {
age: 62, bp: 80, sg: 1.01, al: 3, su: 0, bgr: 148, bu: 86, sc: 3.2,
sod: 135, pot: 4.6, hemo: 9.5, pcv: 28, wbcc: 9800, rbcc: 3.4,
rbc: "abnormal", pc: "abnormal", pcc: "present", ba: "notpresent",
htn: "yes", dm: "yes", cad: "no", appet: "poor", pe: "yes", ane: "yes",
};
const $ = (sel) => document.querySelector(sel);
async function init() {
try {
const meta = await fetch(`${API_BASE}/api/metadata`).then((r) => r.json());
renderModelStrip(meta);
renderFields(meta);
} catch (e) {
$("#modelStrip").innerHTML =
'API offline — start the backend to enable predictions.';
}
$("#predictForm").addEventListener("submit", onSubmit);
$("#exampleBtn").addEventListener("click", loadExample);
}
function renderModelStrip(meta) {
const rf = (meta.metrics && meta.metrics["Random Forest"]) || {};
const chips = [
["Model", "Random Forest"],
["Accuracy", rf.accuracy != null ? rf.accuracy + "%" : "—"],
["Recall", rf.recall != null ? rf.recall + "%" : "—"],
["ROC-AUC", rf.roc_auc != null ? rf.roc_auc : "—"],
["Threshold", meta.threshold],
];
$("#modelStrip").innerHTML = chips
.map(([k, v]) => `${k} ${v}`)
.join("");
}
function renderFields(meta) {
$("#numericFields").innerHTML = meta.numeric_fields
.map(
(f) => `
`
)
.join("");
$("#categoricalFields").innerHTML = meta.categorical_fields
.map((f) => {
const opts = ['']
.concat(f.choices.map((c) => ``))
.join("");
return `
`;
})
.join("");
}
function loadExample() {
for (const [k, v] of Object.entries(EXAMPLE)) {
const el = document.getElementById(k);
if (el) el.value = v;
}
}
function collect() {
const payload = {};
new FormData($("#predictForm")).forEach((value, key) => {
if (value === "" || value == null) return;
const num = Number(value);
payload[key] = Number.isNaN(num) || value.match(/[a-z]/i) ? value : num;
});
return payload;
}
async function onSubmit(evt) {
evt.preventDefault();
const btn = $("#submitBtn");
btn.disabled = true;
btn.textContent = "Analyzing…";
try {
const res = await fetch(`${API_BASE}/api/predict`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(collect()),
});
if (!res.ok) throw new Error(await res.text());
renderResult(await res.json());
} catch (e) {
alert("Prediction failed: " + e.message);
} finally {
btn.disabled = false;
btn.textContent = "Estimate CKD risk";
}
}
function renderResult(r) {
const pct = Math.round(r.probability * 100);
const ring =
r.risk_band === "High" ? "#dc2626" : r.risk_band === "Moderate" ? "#d97706" : "#16a34a";
$("#resultCard").innerHTML = `
${pct}%
${r.prediction}
${r.risk_band} risk
Model probability of CKD: ${pct}% · decision threshold ${r.threshold}
`;
const inds = r.key_indicators || [];
$("#indicators").innerHTML = inds.length
? inds
.map(
(i) => `
${i.label} — ${i.value} (normal ${i.normal_range})
${i.flag.toUpperCase()}
`
)
.join("")
: 'No entered lab values fall outside typical reference ranges.
';
$("#disclaimer").textContent = r.disclaimer;
$("#resultPanel").hidden = false;
$("#resultPanel").scrollIntoView({ behavior: "smooth", block: "nearest" });
}
init();