This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Scikit-Learner is a fully static, single-page web app that runs scikit-learn in the user's browser via Pyodide (CPython compiled to WebAssembly). There is no backend β what looks like API calls in the JS layer are Python function calls dispatched into the in-browser Pyodide runtime. Deployment is just hosting frontend/ on a static host (Netlify, configured in netlify.toml).
The landing/ directory is a separate marketing page; the app proper is frontend/.
python3 -m http.server -d frontend 8080
# then open http://localhost:8080/No build step, no dependency install, no backend process. Pyodide and its packages (scikit-learn, pandas, numpy, scipy, joblib) are fetched from the JSDelivr CDN at runtime β the app requires internet access on first load (~15 MB, ~10 s). Subsequent loads are cached.
pyproject.toml and .python-version exist only because the project was historically a FastAPI backend β they have no role in the current deployment. dependencies = [] is intentional.
The whole app pivots around three files. Understanding the contract between them is the key to being productive here.
frontend/py/learner.pyβ pure Python module. Every former HTTP endpoint is a top-level function (upload_csv,load_sample,train,train_all,predictions,scatter_data,export_model,bulk_zip,comparison, etc.) that takes JSON-serializable args and returns adict(or rawbytesfor downloads). Module-levelcurrent_datadict holds session state β dataframe, trained models, task type. Each browser tab is its own Pyodide instance, so global state is fine.frontend/js/pyodide-bridge.jsβ boots Pyodide, loads packages, copiesdata/airfoil.csvinto the Pyodide MEMFS at/data/airfoil.csv, executeslearner.py, then exposes a tiny surface onwindow:pyCall(fnName, [primitiveArgs])β for JSON-able calls. Builds a Python expression string and runs it viarunPython. Result is converted withtoJs({dict_converter: Object.fromEntries}).pyCallBinary(fnName, Uint8Array, extraArgs)β passes a binary buffer via aglobals.set('__bridge_buf', ...)shim (used byupload_csv).downloadBytes(bytes, filename, mime)β triggers a browser download.window.pyodideReady()and thepyodide-readyevent signal when the runtime is up.
frontend/js/app.jsβ all UI logic. Builds DOM, wires Bootstrap controls, renders Plotly charts. CallspyCall('train', [...])etc. instead offetch(). Waits forpyodide-readybefore its first call. TheAPI_BASEconstant is a vestigial leftover from the FastAPI version and is unused.
- Add a top-level function in
learner.py. It must accept and return only JSON-serializable values (orbytesfor downloads). UseValueErrorfor user-facing errors β the bridge surfaces these as UI error messages. - If a list/dict comes from JS, defensively unwrap PyProxies:
if hasattr(x, "to_py"): x = list(x.to_py()). The bridge passes primitives as Python literals (so JS arrays arrive as Python lists), but uploaded binary args arrive as JsProxy. - From
app.js, call it:const result = await pyCall('your_fn', [arg1, arg2]);. No JS-side schema needed. - Hot-reload caveat: editing
learner.pyrequires a hard-reload (Cmd-Shift-R) for Pyodide to re-import it. A normal reload re-fetches the file but the module stays cached.
Add to AVAILABLE_MODELS (regression) or AVAILABLE_CLASSIFICATION_MODELS (classification) in learner.py. The dict key becomes the model identifier used end-to-end; category controls UI grouping. The UI picks the right dict via the active task_type.
frontend/data/airfoil.csv is shipped in-repo and copied into Pyodide MEMFS at boot. The original FastAPI version used fetch_openml, which doesn't work in-browser. Other sample datasets come from sklearn.datasets (loaded lazily) or are synthesized in load_sample().
- Don't try to add real HTTP endpoints or a Python web server β the entire point is static deployment. New features go through the
pyCallbridge. - 20 MB CSV upload cap is dictated by Pyodide's WASM heap, not by code. Don't add code to "fix" this β document the limit.
- Boston Housing is synthesized, not loaded from sklearn (removed in sklearn β₯1.2). The synthetic generator is in
load_sample()underdataset_key == "boston". - Pyodide version is pinned in
pyodide-bridge.js(PYODIDE_VERSION). Bumping it may shift which scikit-learn version is bundled β verify the model dict still works.
The README mentions a Playwright end-to-end spec covering Pyodide bootstrap, sample loading, training, predictions, export, and the scatter-plot render (8 assertions). The spec file is not currently in the repo β if asked to add tests, ask the user where the spec lives or whether to scaffold a fresh one.