Wrap up — folder structure, deploy, what's next
Recap the 12 stages, see the final folder tree, deploy to Render in 5 minutes, and choose what to learn next.
🧮 Code files for this stage
student_predictor/
app/
main.py # FastAPI app + 4 routes
ml/
train.py # trains model.pkl (Stage 4)
evaluate.py # computes metrics (Stage 5)
error_analysis.py # finds worst predictions (Stage 6)
compare_models.py # Linear vs RF (Stage 7)
model.pkl # the saved winner
templates/
base.html # shared shell (Stage 9)
home.html # the form (Stage 9)
result.html # the prediction (Stage 9)
model.html # /model-info page (Stage 11)
static/
style.css # one stylesheet (Stage 10)
data/
students.csv # the dataset (Stage 1)
requirements.txt
run.sh
render.yaml # Render deploy config
14 files total. ~600 lines of code.
fastapi==0.115.0 uvicorn[standard]==0.30.0 jinja2==3.1.4 pandas==2.2.2 scikit-learn==1.5.1 joblib==1.4.2 python-multipart==0.0.9
services:
- type: web
name: student-performance-predictor
runtime: python
plan: free
buildCommand: pip install -r requirements.txt
startCommand: uvicorn app.main:app --host 0.0.0.0 --port $PORT
healthCheckPath: /docs
autoDeploy: true
#!/bin/bash # One-time setup pip install -r requirements.txt python app/ml/train.py # produces app/ml/model.pkl # Every time you want to run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload # Open http://localhost:8000 in your browser
How it works
The folder structure splits 'training time' (app/ml/) from 'serving time' (app/main.py + app/templates/ + app/static/). train.py runs offline, writes model.pkl, exits. main.py loads model.pkl once and serves predictions forever. That split is what makes deployment trivial — Render only needs to install requirements.txt and run uvicorn; the model is already trained and bundled. WHAT TO LEARN NEXT: (1) Add user accounts (Stage 3 of OpenBenchML's build course covers this — SQLite + JWT). (2) Add a database to save predictions per user. (3) Add a /notebook so users can experiment with the model. (4) Add a leaderboard for who can train the best model. (5) Add WebSocket live updates when a new model is uploaded. That's literally the path from 'student predictor' to 'OpenBenchML'. You just walked it backwards.
FINAL ANSWER on response models: POST /predict -> YES, response_model=PredictResponse POST /predict-form -> NO, returns HTML (response_class=HTMLResponse) GET / -> NO, returns HTML GET /model-info -> NO, returns HTML Rule of thumb: if a route returns JSON, declare a response_model. If it returns HTML, use response_class=HTMLResponse. If it returns a file, use FileResponse. Never mix — the client expects ONE content type per route.
- Committing model.pkl to git — it's a binary, can be 100MB+. Use git-lfs or train on deploy.
- Using `--reload` in production — it watches files and restarts on every change, kills throughput.
- Not setting host='0.0.0.0' in production — Render won't be able to reach your app on 127.0.0.1.