End-to-end ML + web app that predicts Stockholm apartment (bostadsrätt) value from structured features.
- Outputs: predicted SEK/kvm and derived total price
- UI: Next.js (TypeScript + Tailwind)
- API: FastAPI (
POST /predict,GET /health) - Training: scikit-learn experiments → exported artifacts (model + preprocessor)
flowchart LR
subgraph Data["Data"]
direction TB
SCB["SCB export<br/>CSV (raw)"]
SYN["Synthetic generator<br/>backend/scripts/make_synth_data.py"]
PROC["Processed dataset<br/>data/processed/*.csv"]
SCB --> PROC
SYN --> PROC
end
subgraph Training["Training"]
direction TB
PARAMS["Config<br/>params.json / params_full.json"]
RUN["Experiment runner<br/>backend/scripts/run_experiments.py"]
ART["Artifacts<br/>backend/models/model_*.pkl<br/>backend/models/preprocessor_*.pkl"]
MET["Metrics<br/>backend/reports/metrics*/**.json"]
PARAMS --> RUN
PROC --> RUN
RUN --> ART
RUN --> MET
end
subgraph Serving["Serving"]
direction TB
API["FastAPI<br/>POST /predict<br/>GET /health"]
LOGS["Prediction logs<br/>logs/predictions.jsonl<br/>(relative to backend)"]
ART --> API
API --> LOGS
end
subgraph UI["UI"]
direction TB
WEB["Next.js<br/>localhost:3000"]
end
WEB <--> API
- Python 3.11+
- Node.js (CI uses 22)
From repo root (PowerShell):
python -m venv backend/.venv
backend/.venv/Scripts/python -m pip install -U pip
backend/.venv/Scripts/python -m pip install -e backend[dev]This trains a model where all UI inputs influence the prediction.
backend/.venv/Scripts/python backend/scripts/make_synth_data.py --out data/processed/train_full.csv --n 20000
backend/.venv/Scripts/python backend/scripts/run_experiments.py --params params_full.json --model rf --version full_v1Artifacts created:
backend/models/model_full_v1.pklbackend/models/preprocessor_full_v1.pkl
- Place your export at
data/raw/scb.csv(or changeparams.json) - Map columns via
params.json:data.column_map(SCB exports vary)
backend/.venv/Scripts/python backend/scripts/prepare_data.py --params params.json
backend/.venv/Scripts/python backend/scripts/run_experiments.py --params params.json --model baseline --version scb_v1powershell -ExecutionPolicy Bypass -File backend/scripts/run_api_scb.ps1powershell -ExecutionPolicy Bypass -File backend/scripts/run_api_full.ps1Health check:
http://localhost:8000/health(SCB)http://localhost:8001/health(full-feature)
Prediction logs (default): backend/logs/predictions.jsonl
From repo root:
cd frontend
npm ci
npm run devBy default the UI calls http://localhost:8000. To point it at the full-feature API, create frontend/.env.local:
NEXT_PUBLIC_API_BASE_URL=http://localhost:8001Open: http://localhost:3000
Request:
{
"area": 65,
"rooms": 2,
"district": "Södermalm",
"year_built": 1998,
"monthly_fee": 3200,
"transaction_year": 2022
}Response:
{
"predicted_price_per_sqm": 72000,
"predicted_total_price": 4680000,
"model_version": "full_v1",
"inference_ms": 12.3
}- Per-run metrics:
backend/reports/metrics*/run_*.json - Latest metrics:
backend/reports/metrics*/latest.json
GitHub Actions workflow: .github/workflows/ci.yml
- Backend: ruff + pytest
- Frontend: lint + build
- Docker: builds
backend/Dockerfile
- UI loads but no result: change inputs and click Predict again.
- Prediction not changing: the SCB model may not use every UI feature; use the full-feature model for a demo where all inputs affect the output.
- API not reachable: ensure Uvicorn is listening on
8000(SCB) or8001(full-feature).
dvc.yaml is included as a starting point for prepare and train stages.
dvc init
dvc reprodocker build -t spi-backend -f backend/Dockerfile backend
docker run -p 8000:8000 spi-backendThis project is easiest to deploy as:
- Backend API: Render (Docker web service)
- Frontend: Vercel (Next.js)
- Push the repo to GitHub.
- In Render: New → Web Service → connect the repo.
- Select Docker and set Root Directory to
backend. - Add environment variables (example: SCB model):
MODEL_PATH=models/model_scb_v2.pklPREPROCESSOR_PATH=models/preprocessor_scb_v2.pklMODEL_VERSION=scb_v2TARGET_MODE=total_priceMETRICS_PATH=reports/metrics/latest.json- Optional (recommended after frontend is deployed):
CORS_ALLOW_ORIGIN=https://<your-vercel-domain>
Render provides PORT automatically; the container will listen on it.
Verify backend after deploy:
- Open
https://<your-render-service>/health→ should return{ "ok": true } - Open
https://<your-render-service>/model-info
- In Vercel: Add New → Project → import the repo.
- Set Root Directory to
frontend. - Add environment variable:
NEXT_PUBLIC_API_BASE_URL=https://<your-render-service>
- Deploy.
Verify frontend:
- Open
https://<your-vercel-domain> - Run a prediction (tab Predict/Prediktera) and confirm results.
- If the UI shows network errors, confirm
NEXT_PUBLIC_API_BASE_URLis set in Vercel and that the backend/healthis reachable. - If you set
CORS_ALLOW_ORIGIN, ensure it matches your deployed frontend origin exactly (includinghttps://).